diff --git a/examples/blur.nim b/examples/blur.nim index 589e41be..ced56923 100644 --- a/examples/blur.nim +++ b/examples/blur.nim @@ -1,4 +1,4 @@ -import pixie +import pixie, common let trees = readImage("examples/data/trees.png") @@ -19,4 +19,4 @@ blur.draw(mask, blendMode = MaskBlend) image.draw(trees) image.draw(blur) -image.writeFile("examples/blur.png") +image.writeFile(outputPath("blur.png")) diff --git a/examples/common.nim b/examples/common.nim new file mode 100644 index 00000000..475f1762 --- /dev/null +++ b/examples/common.nim @@ -0,0 +1,12 @@ +import os + +proc outputPath*(fileName: string): string = + if paramCount() >= 1: + result = + if paramStr(1) == "--" and paramCount() >= 2: + paramStr(2) + else: + paramStr(1) + createDir(parentDir(result)) + else: + result = "examples" / fileName diff --git a/examples/config.nims b/examples/config.nims new file mode 100644 index 00000000..a1192088 --- /dev/null +++ b/examples/config.nims @@ -0,0 +1 @@ +--path:"../src" diff --git a/examples/gradient.nim b/examples/gradient.nim index 129f7e81..c915ae89 100644 --- a/examples/gradient.nim +++ b/examples/gradient.nim @@ -1,4 +1,4 @@ -import pixie +import pixie, common let image = newImage(200, 200) image.fill(rgba(255, 255, 255, 255)) @@ -26,4 +26,4 @@ image.fillPath( paint ) -image.writeFile("examples/gradient.png") +image.writeFile(outputPath("gradient.png")) diff --git a/examples/heart.nim b/examples/heart.nim index ebff8d87..2a931921 100644 --- a/examples/heart.nim +++ b/examples/heart.nim @@ -1,4 +1,4 @@ -import pixie +import pixie, common let image = newImage(200, 200) image.fill(rgba(255, 255, 255, 255)) @@ -15,4 +15,4 @@ image.fillPath( parseHtmlColor("#FC427B").rgba ) -image.writeFile("examples/heart.png") +image.writeFile(outputPath("heart.png")) diff --git a/examples/image_tiled.nim b/examples/image_tiled.nim index b64d39da..2d8b2e62 100644 --- a/examples/image_tiled.nim +++ b/examples/image_tiled.nim @@ -1,4 +1,4 @@ -import pixie +import pixie, common let image = newImage(200, 200) image.fill(rgba(255, 255, 255, 255)) @@ -15,4 +15,4 @@ paint.image = readImage("examples/data/mandrill.png") paint.imageMat = scale(vec2(0.08, 0.08)) image.fillPath(path, paint) -image.writeFile("examples/image_tiled.png") +image.writeFile(outputPath("image_tiled.png")) diff --git a/examples/line.nim b/examples/line.nim index dc9e7c3a..918256ff 100644 --- a/examples/line.nim +++ b/examples/line.nim @@ -1,4 +1,4 @@ -import pixie +import pixie, common let image = newImage(200, 200) image.fill(rgba(255, 255, 255, 255)) @@ -13,4 +13,4 @@ let ctx.strokeSegment(segment(start, stop)) -image.writeFile("examples/line.png") +image.writeFile(outputPath("line.png")) diff --git a/examples/masking.nim b/examples/masking.nim index db9bf74e..a1ca95c9 100644 --- a/examples/masking.nim +++ b/examples/masking.nim @@ -1,4 +1,4 @@ -import pixie +import pixie, common let image = newImage(200, 200) @@ -29,4 +29,4 @@ mask.fillPath( lines.draw(mask, blendMode = MaskBlend) image.draw(lines) -image.writeFile("examples/masking.png") +image.writeFile(outputPath("masking.png")) diff --git a/examples/rounded_rectangle.nim b/examples/rounded_rectangle.nim index c5006947..f704e83c 100644 --- a/examples/rounded_rectangle.nim +++ b/examples/rounded_rectangle.nim @@ -1,4 +1,4 @@ -import pixie +import pixie, common let image = newImage(200, 200) image.fill(rgba(255, 255, 255, 255)) @@ -13,4 +13,4 @@ let ctx.fillRoundedRect(rect(pos, wh), r) -image.writeFile("examples/rounded_rectangle.png") +image.writeFile(outputPath("rounded_rectangle.png")) diff --git a/examples/shadow.nim b/examples/shadow.nim index 1e4d372c..4d8353bc 100644 --- a/examples/shadow.nim +++ b/examples/shadow.nim @@ -1,4 +1,4 @@ -import pixie +import pixie, common let image = newImage(200, 200) image.fill(rgba(255, 255, 255, 255)) @@ -19,4 +19,4 @@ let shadow = polygonImage.shadow( image.draw(shadow) image.draw(polygonImage) -image.writeFile("examples/shadow.png") +image.writeFile(outputPath("shadow.png")) diff --git a/examples/square.nim b/examples/square.nim index 7c544b24..355df430 100644 --- a/examples/square.nim +++ b/examples/square.nim @@ -1,4 +1,4 @@ -import pixie +import pixie, common let image = newImage(200, 200) image.fill(rgba(255, 255, 255, 255)) @@ -12,4 +12,4 @@ let ctx.fillRect(rect(pos, wh)) -image.writeFile("examples/square.png") +image.writeFile(outputPath("square.png")) diff --git a/examples/text.nim b/examples/text.nim index 5dd45c25..6678e532 100644 --- a/examples/text.nim +++ b/examples/text.nim @@ -1,4 +1,4 @@ -import pixie +import pixie, common let image = newImage(200, 200) image.fill(rgba(255, 255, 255, 255)) @@ -10,4 +10,4 @@ font.paint.color = color(1, 0, 0) let text = "Typesetting is the arrangement and composition of text in graphic design and publishing in both digital and traditional medias." image.fillText(font.typeset(text, vec2(180, 180)), translate(vec2(10, 10))) -image.writeFile("examples/text.png") +image.writeFile(outputPath("text.png")) diff --git a/examples/text.png b/examples/text.png index 890dba1f..8c3e7f60 100644 Binary files a/examples/text.png and b/examples/text.png differ diff --git a/examples/text_spans.nim b/examples/text_spans.nim index 1976c605..7dad2b69 100644 --- a/examples/text_spans.nim +++ b/examples/text_spans.nim @@ -1,4 +1,4 @@ -import pixie +import pixie, common let image = newImage(200, 200) image.fill(rgba(255, 255, 255, 255)) @@ -20,4 +20,4 @@ let spans = @[ ] image.fillText(typeset(spans, vec2(180, 180)), translate(vec2(10, 10))) -image.writeFile("examples/text_spans.png") +image.writeFile(outputPath("text_spans.png")) diff --git a/examples/tiger.nim b/examples/tiger.nim index 157647ea..456384ce 100644 --- a/examples/tiger.nim +++ b/examples/tiger.nim @@ -1,4 +1,4 @@ -import pixie +import pixie, common let image = newImage(200, 200) image.fill(rgba(255, 255, 255, 255)) @@ -12,4 +12,4 @@ image.draw( translate(vec2(-450, -450)) ) -image.writeFile("examples/tiger.png") +image.writeFile(outputPath("tiger.png")) diff --git a/src/pixie/contexts.nim b/src/pixie/contexts.nim index 87822b9c..b5f0ce91 100644 --- a/src/pixie/contexts.nim +++ b/src/pixie/contexts.nim @@ -551,7 +551,7 @@ proc scale*(ctx: Context, x, y: float32) {.inline, raises: [].} = proc rotate*(ctx: Context, angle: float32) {.inline, raises: [].} = ## Adds a rotation to the transformation matrix. - ctx.mat = ctx.mat * rotate(-angle) + ctx.mat = ctx.mat * rotate(angle) proc resetTransform*(ctx: Context) {.inline, raises: [].} = ## Resets the current transform to the identity matrix. diff --git a/src/pixie/fileformats/svg.nim b/src/pixie/fileformats/svg.nim index ac01734b..c4d1b034 100644 --- a/src/pixie/fileformats/svg.nim +++ b/src/pixie/fileformats/svg.nim @@ -261,8 +261,8 @@ proc parseSvgProperties(node: XmlNode, inherited: SvgProperties): SvgProperties m[0, 1] = parseFloat(arr[1]) m[1, 0] = parseFloat(arr[2]) m[1, 1] = parseFloat(arr[3]) - m[2, 0] = parseFloat(arr[4]) - m[2, 1] = parseFloat(arr[5]) + m[0, 2] = parseFloat(arr[4]) + m[1, 2] = parseFloat(arr[5]) result.transform = result.transform * m elif f.startsWith("translate("): let diff --git a/src/pixie/fontformats/opentype.nim b/src/pixie/fontformats/opentype.nim index 14704bff..65ae9160 100644 --- a/src/pixie/fontformats/opentype.nim +++ b/src/pixie/fontformats/opentype.nim @@ -2404,9 +2404,9 @@ proc parseCompositeGlyph(opentype: OpenType, offset: int): Path = var subPath = opentype.parseGlyfGlyph(component.glyphId) subPath.transform(mat3( - component.xScale, component.scale10, 0.0, - component.scale01, component.yScale, 0.0, - component.dx, component.dy, 1.0 + component.xScale, component.scale10, component.dx, + component.scale01, component.yScale, component.dy, + 0.0, 0.0, 1.0 )) result.addPath(subPath) diff --git a/src/pixie/images.nim b/src/pixie/images.nim index c4ec9cf5..65619873 100644 --- a/src/pixie/images.nim +++ b/src/pixie/images.nim @@ -664,18 +664,23 @@ proc draw*( transform = transform * scale(vec2(1/2, 1/2)) let + translation = transform.pos hasRotationOrScaling = not(dx == vec2(1, 0) and dy == vec2(0, 1)) smooth = not( dx.length == 1.0 and dy.length == 1.0 and - transform[2, 0].fractional == 0.0 and - transform[2, 1].fractional == 0.0 + translation.x.fractional == 0.0 and + translation.y.fractional == 0.0 ) if hasRotationOrScaling or smooth: a.drawSmooth(b, transform, blendMode) else: - a.blendRect(b, ivec2(transform[2, 0].int32, transform[2, 1].int32), blendMode) + a.blendRect( + b, + ivec2(translation.x.int32, translation.y.int32), + blendMode + ) proc drawTiled*( dst, src: Image, mat: Mat3, blendMode = NormalBlend diff --git a/src/pixie/paths.nim b/src/pixie/paths.nim index 69a9635e..b0aa3d02 100644 --- a/src/pixie/paths.nim +++ b/src/pixie/paths.nim @@ -839,7 +839,7 @@ proc commandsToShapes( ArcParams( radii: radii, - rotMat: rotate(-radians), + rotMat: rotate(radians), center: center, theta: theta, delta: delta diff --git a/tests/all.nim b/tests/all.nim index a5538335..db8d096b 100644 --- a/tests/all.nim +++ b/tests/all.nim @@ -12,15 +12,4 @@ import test_ppm, test_qoi, test_svg, - ../examples/text, - ../examples/text_spans, - ../examples/square, - ../examples/line, - ../examples/rounded_rectangle, - ../examples/heart, - ../examples/masking, - ../examples/gradient, - ../examples/image_tiled, - ../examples/shadow, - ../examples/blur, - ../examples/tiger + test_examples diff --git a/tests/contexts/setTransform_2.png b/tests/contexts/setTransform_2.png new file mode 100644 index 00000000..489707b2 Binary files /dev/null and b/tests/contexts/setTransform_2.png differ diff --git a/tests/test_contexts.nim b/tests/test_contexts.nim index c547c16f..32b9eadd 100644 --- a/tests/test_contexts.nim +++ b/tests/test_contexts.nim @@ -201,7 +201,7 @@ block: block: let ctx = newContext(newImage(300, 150)) - ctx.setTransform(mat3(1, 0.2, 0, 0.8, 1, 0, 0, 0, 1)) + ctx.setTransform(mat3(1, 0.8, 0, 0.2, 1, 0, 0, 0, 1)) ctx.fillRect(0, 0, 100, 100) ctx.image.xray("tests/contexts/setTransform_1.png") @@ -209,10 +209,10 @@ block: block: let ctx = newContext(newImage(300, 150)) - ctx.setTransform(mat3(1, 0.2, 0, 0.8, 1, 0, 0, 0, 1)) + ctx.setTransform(mat3(1, 0.8, 0, 0.2, 1, 0, 0, 0, 1)) ctx.fillRect(0, 0, 100, 100) - ctx.image.xray("tests/contexts/resetTransform_1.png") + ctx.image.xray("tests/contexts/setTransform_2.png") block: let ctx = newContext(newImage(300, 150)) @@ -225,7 +225,7 @@ block: block: let ctx = newContext(newImage(300, 150)) - ctx.transform(mat3(1, 0, 0, 1.7, 1, 0, 0, 0, 1)) + ctx.transform(mat3(1, 1.7, 0, 0, 1, 0, 0, 0, 1)) ctx.fillStyle = "gray" ctx.fillRect(40, 40, 50, 20) ctx.fillRect(40, 90, 50, 20) diff --git a/tests/test_examples.nim b/tests/test_examples.nim new file mode 100644 index 00000000..72885010 --- /dev/null +++ b/tests/test_examples.nim @@ -0,0 +1,51 @@ +import os, osproc, strformat, pixie + +const examples = [ + "text", + "text_spans", + "square", + "line", + "rounded_rectangle", + "heart", + "masking", + "gradient", + "image_tiled", + "shadow", + "blur", + "tiger" +] + +const updateThreshold = 1.0 + +let + generatedDir = "tmp/generated/examples" + xrayDir = "tmp/xray/examples" + +createDir(generatedDir) +createDir(xrayDir) + +for example in examples: + let + nimFile = &"examples/{example}.nim" + masterPath = &"examples/{example}.png" + generatedPath = generatedDir / &"{example}.png" + xrayPath = xrayDir / &"{example}.png" + run = execCmdEx( + &"nim r -d:release {nimFile} -- {generatedPath}", + workingDir = getCurrentDir() + ) + + if run.exitCode != 0: + echo run.output + raise newException(PixieError, &"Example {example} failed with exit code {run.exitCode}") + + let + generated = readImage(generatedPath) + master = readImage(masterPath) + (score, xray) = diff(generated, master) + + xray.writeFile(xrayPath) + echo &"xray {masterPath} -> {score:0.6f}" + + if score > updateThreshold: + copyFile(generatedPath, masterPath) diff --git a/tests/test_images.nim b/tests/test_images.nim index 50ac13e3..bcf4fc62 100644 --- a/tests/test_images.nim +++ b/tests/test_images.nim @@ -195,9 +195,9 @@ block: a.draw( b, mat3( - -0.5, -4.371138828673793e-008, 0.0, - -4.371138828673793e-008, 0.5, 0.0, - 292.0, 45.0, 1.0 + -0.5, -4.371138828673793e-008, 292.0, + -4.371138828673793e-008, 0.5, 45.0, + 0.0, 0.0, 1.0 ) ) diff --git a/tests/test_images_draw.nim b/tests/test_images_draw.nim index e154d5c2..dc8a263c 100644 --- a/tests/test_images_draw.nim +++ b/tests/test_images_draw.nim @@ -143,7 +143,7 @@ block: b = newImage(50, 50) a.fill(rgba(255, 255, 255, 255)) b.fill(rgbx(0, 0, 0, 255)) - a.draw(b, translate(vec2(0, 50)) * rotate(45.toRadians)) + a.draw(b, translate(vec2(0, 50)) * rotate(-45.toRadians)) a.xray("tests/images/masters/smooth2.png") block: @@ -170,7 +170,7 @@ block: b = newImage(10, 10) a.fill(rgba(255, 255, 255, 255)) b.fill(rgbx(255, 0, 0, 255)) - let m = translate(vec2(50, 50)) * rotate(30.toRadians) + let m = translate(vec2(50, 50)) * rotate(-30.toRadians) a.draw(b, m) a.xray("tests/images/masters/smooth5.png") @@ -179,7 +179,7 @@ block: a = newImage(100, 100) b = readImage(&"tests/images/turtle.png") a.fill(rgba(255, 255, 255, 255)) - let m = translate(vec2(50, 50)) * rotate(30.toRadians) + let m = translate(vec2(50, 50)) * rotate(-30.toRadians) a.draw(b, m) a.xray("tests/images/masters/smooth6.png") @@ -188,7 +188,7 @@ block: a = newImage(100, 100) b = readImage(&"tests/images/turtle@10x.png") a.fill(rgba(255, 255, 255, 255)) - let m = translate(vec2(50, 50)) * rotate(30.toRadians) * scale(vec2(0.1, 0.1)) + let m = translate(vec2(50, 50)) * rotate(-30.toRadians) * scale(vec2(0.1, 0.1)) a.draw(b, m) a.xray("tests/images/masters/smooth7.png") @@ -225,7 +225,7 @@ block: b = readImage(&"tests/images/turtle.png") a.fill(rgba(255, 255, 255, 255)) let m = translate(vec2(-43.29, -103.87)) * - rotate(-15.toRadians) * + rotate(15.toRadians) * scale(vec2(263.86/40, 263.86/40)) a.draw(b, m) a.xray("tests/images/masters/smooth11.png") @@ -235,7 +235,7 @@ block: a = newImage(100, 100) b = readImage(&"tests/images/turtle.png") a.fill(rgba(255, 255, 255, 255)) - let m = translate(vec2(50, 50)) * rotate(-5.toRadians) + let m = translate(vec2(50, 50)) * rotate(5.toRadians) a.draw(b, m * translate(vec2(0, 0))) a.draw(b, m * translate(vec2(-40, 0))) a.draw(b, m * translate(vec2(-40, -40)))