From 68e2522074a6d8fdb557a585064a95905c9058f7 Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Sun, 14 Feb 2021 12:22:38 -0600 Subject: [PATCH 1/4] performance improvements --- src/pixie/images.nim | 2 +- src/pixie/masks.nim | 2 +- src/pixie/paths.nim | 149 ++++++++++++++++++++++++------------------- 3 files changed, 87 insertions(+), 66 deletions(-) diff --git a/src/pixie/images.nim b/src/pixie/images.nim index 3a2efd2..2f6e876 100644 --- a/src/pixie/images.nim +++ b/src/pixie/images.nim @@ -68,7 +68,7 @@ proc `[]=`*(image: Image, x, y: int, rgba: ColorRGBA) {.inline.} = if image.inside(x, y): image.setRgbaUnsafe(x, y, rgba) -proc fillUnsafe(data: var seq[ColorRGBA], rgba: ColorRGBA, start, len: int) = +proc fillUnsafe*(data: var seq[ColorRGBA], rgba: ColorRGBA, start, len: int) = ## Fills the image data with the parameter color starting at index start and ## continuing for len indices. diff --git a/src/pixie/masks.nim b/src/pixie/masks.nim index e5af319..974983c 100644 --- a/src/pixie/masks.nim +++ b/src/pixie/masks.nim @@ -84,7 +84,7 @@ proc minifyBy2*(mask: Mask, power = 1): Mask = mask.getValueUnsafe(x * 2 + 0, y * 2 + 1) result.setValueUnsafe(x, y, (value div 4).uint8) -proc fillUnsafe(data: var seq[uint8], value: uint8, start, len: int) = +proc fillUnsafe*(data: var seq[uint8], value: uint8, start, len: int) = ## Fills the mask data with the parameter value starting at index start and ## continuing for len indices. nimSetMem(data[start].addr, value.cint, len) diff --git a/src/pixie/paths.nim b/src/pixie/paths.nim index 9d10e28..7081239 100644 --- a/src/pixie/paths.nim +++ b/src/pixie/paths.nim @@ -382,7 +382,7 @@ proc roundedRect*( h = wh.y s = splineCircleK - maxRadius = min(w/2, h/2) + maxRadius = min(w / 2, h / 2) nw = min(nw, maxRadius) ne = min(ne, maxRadius) se = min(se, maxRadius) @@ -397,14 +397,14 @@ proc roundedRect*( l1 = vec2(x, y + h - sw) l2 = vec2(x, y + nw) - t1h = t1 + vec2(-nw*s, 0) - t2h = t2 + vec2(+ne*s, 0) - r1h = r1 + vec2(0, -ne*s) - r2h = r2 + vec2(0, +se*s) - b1h = b1 + vec2(+se*s, 0) - b2h = b2 + vec2(-sw*s, 0) - l1h = l1 + vec2(0, +sw*s) - l2h = l2 + vec2(0, -nw*s) + t1h = t1 + vec2(-nw * s, 0) + t2h = t2 + vec2(+ne * s, 0) + r1h = r1 + vec2(0, -ne * s) + r2h = r2 + vec2(0, +se * s) + b1h = b1 + vec2(+se * s, 0) + b2h = b2 + vec2(-sw * s, 0) + l1h = l1 + vec2(0, +sw * s) + l2h = l2 + vec2(0, -nw * s) if clockwise: path.moveTo(t1) @@ -865,13 +865,11 @@ proc partitionSegments( segmentCount += shape.len - 1 let - maxPartitions = max(1, height div 10) - numPartitions = min(maxPartitions, max(1, segmentCount div 10)) - - result.setLen(numPartitions) - - let partitionHeight = height div numPartitions + maxPartitions = max(1, height div 10).uint32 + numPartitions = min(maxPartitions, max(1, segmentCount div 10).uint32) + partitionHeight = (height.uint32 div numPartitions) + var partitions = newSeq[seq[(Segment, int16)]](numPartitions) for shape in shapes: for segment in shape.segments: if segment.at.y == segment.to.y: # Skip horizontal @@ -884,41 +882,46 @@ proc partitionSegments( winding = -1 if partitionHeight == 0: - result[0].add((segment, winding)) + partitions[0].add((segment, winding)) else: - let - atPartition = max(0, segment.at.y).int div partitionHeight - toPartition = max(0, ceil(segment.to.y)).int div partitionHeight - for i in min(atPartition, result.high) .. min(toPartition, result.high): - result[i].add((segment, winding)) + var + atPartition = max(0, segment.at.y).uint32 div partitionHeight + toPartition = max(0, ceil(segment.to.y)).uint32 div partitionHeight + atPartition = clamp(atPartition, 0, partitions.high.uint32) + toPartition = clamp(toPartition, 0, partitions.high.uint32) + for i in atPartition .. toPartition: + partitions[i].add((segment, winding)) + + partitions proc computeCoverages( coverages: var seq[uint8], hits: var seq[(float32, int16)], + numHits: var int, size: Vec2, y: int, partitions: seq[seq[(Segment, int16)]], + partitionHeight: uint32, windingRule: WindingRule -) = +) {.inline.} = const quality = 5 # Must divide 255 cleanly (1, 3, 5, 15, 17, 51, 85) - sampleCoverage = 255.uint8 div quality - ep = 0.0001 * PI + sampleCoverage = (255 div quality).uint8 offset = 1 / quality.float32 initialOffset = offset / 2 - var numHits: int - let - partitionHeight = size.y.int div partitions.len partition = if partitionHeight == 0: - 0 + 0.uint32 else: - min(y div partitionHeight, partitions.high) + min(y.uint32 div partitionHeight, partitions.high.uint32) + + zeroMem(coverages[0].addr, coverages.len) # Do scanlines for this row for m in 0 ..< quality: + const ep = 0.0001 * PI let yLine = y.float32 + initialOffset + offset * m.float32 + ep scanline = Line(a: vec2(0, yLine), b: vec2(size.x, yLine)) @@ -940,9 +943,9 @@ proc computeCoverages( for i in 0 ..< numHits: let (at, winding) = hits[i] - var - fillStart = x.int - leftCover = if at.int - x.int > 0: trunc(x) + 1 - x else: at - x + var fillStart = x.int + + let leftCover = if at.int - x.int > 0: trunc(x) + 1 - x else: at - x if leftCover != 0: inc fillStart if shouldFill(windingRule, count): @@ -978,7 +981,9 @@ proc fillShapes( windingRule: WindingRule, blendMode: BlendMode ) = - let partitions = partitionSegments(shapes, image.height) + let + partitions = partitionSegments(shapes, image.height) + partitionHeight = image.height.uint32 div partitions.len.uint32 # Figure out the total bounds of all the shapes, # rasterize only within the total bounds @@ -995,17 +1000,17 @@ proc fillShapes( var coverages = newSeq[uint8](image.width) hits = newSeq[(float32, int16)](4) + numHits: int for y in startY ..< stopY: - # Reset buffer for this row - zeroMem(coverages[0].addr, coverages.len) - computeCoverages( coverages, hits, + numHits, image.wh, y, partitions, + partitionHeight, windingRule ) @@ -1023,34 +1028,45 @@ proc fillShapes( var coverage = mm_loadu_si128(coverages[x].addr) coverage = mm_and_si128(coverage, first32) - let eqZero = mm_cmpeq_epi16(coverage, mm_setzero_si128()) + let + index = image.dataIndex(x, y) + eqZero = mm_cmpeq_epi16(coverage, mm_setzero_si128()) if mm_movemask_epi8(eqZero) != 0xffff: # If the coverages are not all zero - var source = vColor - - if mm_movemask_epi8(mm_cmpeq_epi32(coverage, first32)) != 0xffff: - # If the coverages are not all 255 + if mm_movemask_epi8(mm_cmpeq_epi32(coverage, first32)) == 0xffff: + # Coverages are all 255 + if color.a == 255 and blendMode == bmNormal: + mm_storeu_si128(image.data[index].addr, vColor) + else: + let backdrop = mm_loadu_si128(image.data[index].addr) + mm_storeu_si128( + image.data[index].addr, + blenderSimd(backdrop, vColor) + ) + else: + # Coverages are not all 255 coverage = unpackAlphaValues(coverage) # Shift the coverages from `a` to `g` and `a` for multiplying coverage = mm_or_si128(coverage, mm_srli_epi32(coverage, 16)) var - colorEven = mm_slli_epi16(source, 8) - colorOdd = mm_and_si128(source, oddMask) + source = vColor + sourceEven = mm_slli_epi16(source, 8) + sourceOdd = mm_and_si128(source, oddMask) - colorEven = mm_mulhi_epu16(colorEven, coverage) - colorOdd = mm_mulhi_epu16(colorOdd, coverage) + sourceEven = mm_mulhi_epu16(sourceEven, coverage) + sourceOdd = mm_mulhi_epu16(sourceOdd, coverage) - colorEven = mm_srli_epi16(mm_mulhi_epu16(colorEven, div255), 7) - colorOdd = mm_srli_epi16(mm_mulhi_epu16(colorOdd, div255), 7) + sourceEven = mm_srli_epi16(mm_mulhi_epu16(sourceEven, div255), 7) + sourceOdd = mm_srli_epi16(mm_mulhi_epu16(sourceOdd, div255), 7) - source = mm_or_si128(colorEven, mm_slli_epi16(colorOdd, 8)) - - let - index = image.dataIndex(x, y) - backdrop = mm_loadu_si128(image.data[index].addr) - mm_storeu_si128(image.data[index].addr, blenderSimd(backdrop, source)) + source = mm_or_si128(sourceEven, mm_slli_epi16(sourceOdd, 8)) + let backdrop = mm_loadu_si128(image.data[index].addr) + mm_storeu_si128( + image.data[index].addr, + blenderSimd(backdrop, source) + ) x += 4 while x < image.width: @@ -1069,8 +1085,12 @@ proc fillShapes( source.b = ((color.b.uint16 * coverage) div 255).uint8 source.a = ((color.a.uint16 * coverage) div 255).uint8 - let backdrop = image.getRgbaUnsafe(x, y) - image.setRgbaUnsafe(x, y, blender(backdrop, source)) + if source.a == 255 and blendMode == bmNormal: + # Skip blending + image.setRgbaUnsafe(x, y, source) + else: + let backdrop = image.getRgbaUnsafe(x, y) + image.setRgbaUnsafe(x, y, blender(backdrop, source)) inc x proc fillShapes( @@ -1078,7 +1098,9 @@ proc fillShapes( shapes: seq[seq[Vec2]], windingRule: WindingRule ) = - let partitions = partitionSegments(shapes, mask.height) + let + partitions = partitionSegments(shapes, mask.height) + partitionHeight = mask.height.uint32 div partitions.len.uint32 # Figure out the total bounds of all the shapes, # rasterize only within the total bounds @@ -1088,24 +1110,23 @@ proc fillShapes( startY = max(0, bounds.y.int) stopY = min(mask.height, (bounds.y + bounds.h).int) - var - coverages = newSeq[uint8](mask.width) - hits = newSeq[(float32, int16)](4) - - when defined(amd64) and not defined(pixieNoSimd): let maskerSimd = bmNormal.maskerSimd() - for y in startY ..< stopY: - # Reset buffer for this row - zeroMem(coverages[0].addr, coverages.len) + var + coverages = newSeq[uint8](mask.width) + hits = newSeq[(float32, int16)](4) + numHits: int + for y in startY ..< stopY: computeCoverages( coverages, hits, + numHits, mask.wh, y, partitions, + partitionHeight, windingRule ) From f2b3c67d4b10dff4a7a62c57ff54ecec89da2317 Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Sun, 14 Feb 2021 12:27:32 -0600 Subject: [PATCH 2/4] morepretty --- examples/blur.nim | 4 ++-- examples/heart.nim | 2 +- examples/rounded_rectangle.nim | 2 +- examples/shadow.nim | 4 ++-- examples/square.nim | 2 +- examples/tiger.nim | 2 +- experiments/benchmark_cairo.nim | 2 +- experiments/benchmark_svg_cairo.nim | 2 +- experiments/svg_cairo.nim | 4 ++-- experiments/test_svg_cairo.nim | 2 +- experiments/trapezoid.nim | 13 ++++++------ src/pixie.nim | 8 ++++---- src/pixie/blends.nim | 22 +++++++++++++-------- src/pixie/fileformats/bmp.nim | 2 +- src/pixie/fileformats/jpg.nim | 2 +- src/pixie/fileformats/png.nim | 4 ++-- src/pixie/fileformats/svg.nim | 4 ++-- src/pixie/images.nim | 10 ++++++---- src/pixie/masks.nim | 2 +- src/pixie/paths.nim | 11 ++++++----- tests/benchmark_blends.nim | 2 +- tests/benchmark_drawCorrect_vs_drawUber.nim | 4 ++-- tests/benchmark_images.nim | 2 +- tests/benchmark_images_draw.nim | 2 +- tests/benchmark_jpg.nim | 2 +- tests/benchmark_masks.nim | 2 +- tests/benchmark_paths.nim | 2 +- tests/benchmark_png.nim | 4 ++-- tests/benchmark_svg.nim | 2 +- tests/fuzz_jpg.nim | 2 +- tests/fuzz_paths.nim | 2 +- tests/fuzz_png.nim | 2 +- tests/fuzz_svg.nim | 2 +- tests/test_bmp.nim | 5 +++-- tests/test_images.nim | 2 +- tests/test_paths.nim | 2 +- tests/test_png.nim | 2 +- tests/test_svg.nim | 2 +- tests/validate_png.nim | 2 +- tools/gen_readme.nim | 5 +++-- 40 files changed, 82 insertions(+), 72 deletions(-) diff --git a/examples/blur.nim b/examples/blur.nim index aa34a4f..ff6d364 100644 --- a/examples/blur.nim +++ b/examples/blur.nim @@ -1,4 +1,4 @@ -import pixie, chroma, vmath +import chroma, pixie, vmath let trees = readImage("examples/data/trees.png") @@ -8,7 +8,7 @@ let image.fill(rgba(255, 255, 255, 255)) var p: Path -p.polygon(100, 100, 70, sides=6) +p.polygon(100, 100, 70, sides = 6) p.closePath() let mask = newMask(200, 200) diff --git a/examples/heart.nim b/examples/heart.nim index 6397cee..79251c1 100644 --- a/examples/heart.nim +++ b/examples/heart.nim @@ -1,4 +1,4 @@ -import pixie, chroma, vmath +import chroma, pixie, vmath let image = newImage(200, 200) diff --git a/examples/rounded_rectangle.nim b/examples/rounded_rectangle.nim index 63bb3cb..598ea92 100644 --- a/examples/rounded_rectangle.nim +++ b/examples/rounded_rectangle.nim @@ -1,4 +1,4 @@ -import pixie, chroma, vmath +import chroma, pixie, vmath let image = newImage(200, 200) image.fill(rgba(255, 255, 255, 255)) diff --git a/examples/shadow.nim b/examples/shadow.nim index db78970..8922f79 100644 --- a/examples/shadow.nim +++ b/examples/shadow.nim @@ -1,4 +1,4 @@ -import pixie, chroma, vmath +import chroma, pixie, vmath let trees = readImage("examples/data/trees.png") @@ -7,7 +7,7 @@ let image.fill(rgba(255, 255, 255, 255)) var p: Path -p.polygon(100, 100, 70, sides=8) +p.polygon(100, 100, 70, sides = 8) p.closePath() var polyImage = newImage(200, 200) diff --git a/examples/square.nim b/examples/square.nim index 528d3ab..43f2fe5 100644 --- a/examples/square.nim +++ b/examples/square.nim @@ -1,4 +1,4 @@ -import pixie, chroma +import chroma, pixie var image = newImage(200, 200) image.fill(rgba(255, 255, 255, 255)) diff --git a/examples/tiger.nim b/examples/tiger.nim index 00f741e..1d0e3a4 100644 --- a/examples/tiger.nim +++ b/examples/tiger.nim @@ -1,4 +1,4 @@ -import pixie, chroma, vmath +import chroma, pixie, vmath let image = newImage(200, 200) image.fill(rgba(255, 255, 255, 255)) diff --git a/experiments/benchmark_cairo.nim b/experiments/benchmark_cairo.nim index b9b6cfe..062f2ba 100644 --- a/experiments/benchmark_cairo.nim +++ b/experiments/benchmark_cairo.nim @@ -1,4 +1,4 @@ -import cairo, math, benchy, pixie, chroma +import benchy, cairo, chroma, math, pixie var surface = imageSurfaceCreate(FORMAT_ARGB32, 1000, 1000) diff --git a/experiments/benchmark_svg_cairo.nim b/experiments/benchmark_svg_cairo.nim index 51ebf16..f8feb05 100644 --- a/experiments/benchmark_svg_cairo.nim +++ b/experiments/benchmark_svg_cairo.nim @@ -1,4 +1,4 @@ -import svg_cairo, benchy +import benchy, svg_cairo let data = readFile("tests/images/svg/Ghostscript_Tiger.svg") diff --git a/experiments/svg_cairo.nim b/experiments/svg_cairo.nim index db910aa..e0b9900 100644 --- a/experiments/svg_cairo.nim +++ b/experiments/svg_cairo.nim @@ -1,7 +1,7 @@ ## Load and Save SVG files. -import chroma, pixie/images, pixie/common, pixie/paths, vmath, xmlparser, - xmltree, strutils, strutils, cairo +import cairo, chroma, pixie/common, pixie/images, pixie/paths, strutils, vmath, + xmlparser, xmltree type Path = paths.Path diff --git a/experiments/test_svg_cairo.nim b/experiments/test_svg_cairo.nim index 20efe86..5e54f94 100644 --- a/experiments/test_svg_cairo.nim +++ b/experiments/test_svg_cairo.nim @@ -1,4 +1,4 @@ -import svg_cairo, pixie, strformat +import pixie, strformat, svg_cairo const files = [ "line01", diff --git a/experiments/trapezoid.nim b/experiments/trapezoid.nim index e9146de..e99b589 100644 --- a/experiments/trapezoid.nim +++ b/experiments/trapezoid.nim @@ -1,10 +1,9 @@ - -import pixie, pixie/paths, pixie/images, chroma, print, vmath, algorithm, sequtils, bumpy +import algorithm, bumpy, chroma, pixie, pixie/images, pixie/paths, print, + sequtils, vmath printColors = false - proc intersectsInner*(a, b: Segment, at: var Vec2): bool {.inline.} = ## Checks if the a segment intersects b segment. ## If it returns true, at will have point of intersection @@ -73,7 +72,7 @@ proc pathToTrapezoids(p: Path): seq[Trapezoid] = var collision = false for y in yScanLines: var at: Vec2 - if intersects(line(vec2(0,y), vec2(1,y)), s, at): + if intersects(line(vec2(0, y), vec2(1, y)), s, at): at = at.roundBy(q) at.y = y if s.at.y != at.y and s.to.y != at.y: @@ -136,9 +135,9 @@ proc pathToTrapezoids(p: Path): seq[Trapezoid] = Trapezoid( nw: a.at, ne: b.at, - se: b.to,# + vec2(0,0.7), - sw: a.to# + vec2(0,0.7) - ) + se: b.to, # + vec2(0,0.7), + sw: a.to # + vec2(0,0.7) + ) ) proc trapFill(image: Image, t: Trapezoid, color: ColorRGBA) = diff --git a/src/pixie.nim b/src/pixie.nim index 889f583..38ad7d7 100644 --- a/src/pixie.nim +++ b/src/pixie.nim @@ -1,8 +1,8 @@ -import pixie/images, pixie/masks, pixie/paths, pixie/common, pixie/blends, - pixie/fileformats/bmp, pixie/fileformats/png, pixie/fileformats/jpg, - pixie/fileformats/svg, flatty/binny, os, pixie/gradients +import flatty/binny, os, pixie/blends, pixie/common, pixie/fileformats/bmp, + pixie/fileformats/jpg, pixie/fileformats/png, pixie/fileformats/svg, + pixie/images, pixie/masks, pixie/paths -export images, masks, paths, common, blends, gradients +export blends, common, images, masks, paths type FileFormat* = enum diff --git a/src/pixie/blends.nim b/src/pixie/blends.nim index b992f6b..7c401fb 100644 --- a/src/pixie/blends.nim +++ b/src/pixie/blends.nim @@ -1,5 +1,6 @@ ## Blending modes. -import chroma, math, common + +import chroma, common, math when defined(amd64) and not defined(pixieNoSimd): import nimsimd/sse2 @@ -28,8 +29,8 @@ type bmColor bmLuminosity - bmMask ## Special blend mode that is used for masking - bmOverwrite ## Special blend mode that just copies pixels + bmMask ## Special blend mode that is used for masking + bmOverwrite ## Special blend mode that just copies pixels bmSubtractMask ## Inverse mask bmIntersectMask bmExcludeMask @@ -141,9 +142,9 @@ proc ClipColor(C: var Color) {.inline.} = n = min([C.r, C.g, C.b]) x = max([C.r, C.g, C.b]) if n < 0: - C = L + (((C - L) * L) / (L - n)) + C = L + (((C - L) * L) / (L - n)) if x > 1: - C = L + (((C - L) * (1 - L)) / (x - L)) + C = L + (((C - L) * (1 - L)) / (x - L)) proc SetLum(C: Color, l: float32): Color {.inline.} = let d = l - Lum(C) @@ -297,7 +298,12 @@ proc blendSoftLight(backdrop, source: ColorRGBA): ColorRGBA = when defined(amd64) and not defined(pixieNoSimd): let - vb = mm_setr_ps(backdrop.r.float32, backdrop.g.float32, backdrop.b.float32, 0) + vb = mm_setr_ps( + backdrop.r.float32, + backdrop.g.float32, + backdrop.b.float32, + 0 + ) vs = mm_setr_ps(source.r.float32, source.g.float32, source.b.float32, 0) v2 = mm_set1_ps(2) v255 = mm_set1_ps(255) @@ -519,8 +525,8 @@ when defined(amd64) and not defined(pixieNoSimd): proc unpackAlphaValues*(v: M128i): M128i {.inline.} = ## Unpack the first 32 bits into 4 rgba(0, 0, 0, value) let - first32 = cast[M128i]([uint32.high, 0, 0, 0]) # First 32 bits - alphaMask = mm_set1_epi32(cast[int32](0xff000000)) # Only `a` + first32 = cast[M128i]([uint32.high, 0, 0, 0]) # First 32 bits + alphaMask = mm_set1_epi32(cast[int32](0xff000000)) # Only `a` result = mm_shuffle_epi32(v, MM_SHUFFLE(0, 0, 0, 0)) diff --git a/src/pixie/fileformats/bmp.nim b/src/pixie/fileformats/bmp.nim index d705bd0..5be8d1f 100644 --- a/src/pixie/fileformats/bmp.nim +++ b/src/pixie/fileformats/bmp.nim @@ -1,4 +1,4 @@ -import flatty/binny, chroma, pixie/common, pixie/images +import chroma, flatty/binny, pixie/common, pixie/images # See: https://en.wikipedia.org/wiki/BMP_file_format diff --git a/src/pixie/fileformats/jpg.nim b/src/pixie/fileformats/jpg.nim index 9070ae5..9cd1b37 100644 --- a/src/pixie/fileformats/jpg.nim +++ b/src/pixie/fileformats/jpg.nim @@ -1,4 +1,4 @@ -import pixie/images, pixie/common +import pixie/common, pixie/images when defined(pixieUseStb): import pixie/fileformats/stb_image/stb_image diff --git a/src/pixie/fileformats/png.nim b/src/pixie/fileformats/png.nim index c98ce41..0f2d9d3 100644 --- a/src/pixie/fileformats/png.nim +++ b/src/pixie/fileformats/png.nim @@ -1,5 +1,5 @@ -import chroma, pixie/common, math, zippy, zippy/crc, flatty/binny, - pixie/images, pixie/masks +import chroma, flatty/binny, math, pixie/common, pixie/images, pixie/masks, + zippy, zippy/crc # See http://www.libpng.org/pub/png/spec/1.2/PNG-Contents.html diff --git a/src/pixie/fileformats/svg.nim b/src/pixie/fileformats/svg.nim index 45bdf98..ef66800 100644 --- a/src/pixie/fileformats/svg.nim +++ b/src/pixie/fileformats/svg.nim @@ -1,7 +1,7 @@ ## Load and Save SVG files. -import chroma, pixie/images, pixie/common, pixie/paths, vmath, xmlparser, - xmltree, strutils, strutils +import chroma, pixie/common, pixie/images, pixie/paths, strutils, vmath, + xmlparser, xmltree const svgSignature* = "= scanline.a.y: var at: Vec2 - if scanline.intersects(segment, at):# and segment.to != at: + if scanline.intersects(segment, at): # and segment.to != at: if numHits == hits.len: hits.setLen(hits.len * 2) hits[numHits] = (at.x.clamp(0, scanline.b.x), winding) @@ -1153,6 +1153,7 @@ proc fillShapes( if peeked == 0: x += 8 continue + let coverage = coverages[x] if coverage != 0: let diff --git a/tests/benchmark_blends.nim b/tests/benchmark_blends.nim index b82c87a..a1e25fc 100644 --- a/tests/benchmark_blends.nim +++ b/tests/benchmark_blends.nim @@ -1,4 +1,4 @@ -import benchy, chroma, vmath, pixie/images +import benchy, chroma, pixie/images, vmath include pixie/blends diff --git a/tests/benchmark_drawCorrect_vs_drawUber.nim b/tests/benchmark_drawCorrect_vs_drawUber.nim index 5b16cc3..b8108c8 100644 --- a/tests/benchmark_drawCorrect_vs_drawUber.nim +++ b/tests/benchmark_drawCorrect_vs_drawUber.nim @@ -1,4 +1,5 @@ -import chroma, vmath, benchy +import benchy, chroma, vmath + include pixie/images block: @@ -23,7 +24,6 @@ block: a.drawUber(b, translate(vec2(25, 25)), bmNormal) keep(b) - block: let a = newImage(1000, 1000) diff --git a/tests/benchmark_images.nim b/tests/benchmark_images.nim index 7ace876..8112f1b 100644 --- a/tests/benchmark_images.nim +++ b/tests/benchmark_images.nim @@ -1,4 +1,4 @@ -import chroma, pixie, benchy +import benchy, chroma, pixie let image = newImage(2560, 1440) diff --git a/tests/benchmark_images_draw.nim b/tests/benchmark_images_draw.nim index 53ff723..3d34544 100644 --- a/tests/benchmark_images_draw.nim +++ b/tests/benchmark_images_draw.nim @@ -1,4 +1,4 @@ -import pixie, chroma, vmath, benchy +import benchy, chroma, pixie, vmath block: let diff --git a/tests/benchmark_jpg.nim b/tests/benchmark_jpg.nim index 0b9524e..6420e2a 100644 --- a/tests/benchmark_jpg.nim +++ b/tests/benchmark_jpg.nim @@ -1,4 +1,4 @@ -import pixie/fileformats/jpg, benchy +import benchy, pixie/fileformats/jpg let data = readFile("tests/images/jpg/jpeg420exif.jpg") diff --git a/tests/benchmark_masks.nim b/tests/benchmark_masks.nim index 2c04b3d..f92e8d9 100644 --- a/tests/benchmark_masks.nim +++ b/tests/benchmark_masks.nim @@ -1,4 +1,4 @@ -import chroma, pixie, benchy +import benchy, chroma, pixie let mask = newMask(2560, 1440) diff --git a/tests/benchmark_paths.nim b/tests/benchmark_paths.nim index 18fc471..7f483e3 100644 --- a/tests/benchmark_paths.nim +++ b/tests/benchmark_paths.nim @@ -1,4 +1,4 @@ -import pixie/paths, benchy +import benchy, pixie/paths let pathStr = "m57.611-8.591c-1.487,1.851-4.899,4.42-1.982,6.348,0.194,0.129,0.564,0.133,0.737-0.001,2.021-1.565,4.024-2.468,6.46-3.05,0.124-0.029,0.398,0.438,0.767,0.277,1.613-0.703,3.623-0.645,4.807-1.983,3.767,0.224,7.332-0.892,10.723-2.2,1.161-0.448,2.431-1.007,3.632-1.509,1.376-0.576,2.58-1.504,3.692-2.645,0.133-0.136,0.487-0.046,0.754-0.046-0.04-0.863,0.922-0.99,1.169-1.612,0.092-0.232-0.058-0.628,0.075-0.73,2.138-1.63,3.058-3.648,1.889-6.025-0.285-0.578-0.534-1.196-1.1-1.672-1.085-0.911-2.187-0.057-3.234-0.361-0.159,0.628-0.888,0.456-1.274,0.654-0.859,0.439-2.192-0.146-3.051,0.292-1.362,0.695-2.603,0.864-4.025,1.241-0.312,0.082-1.09-0.014-1.25,0.613-0.134-0.134-0.282-0.368-0.388-0.346-1.908,0.396-3.168,0.61-4.469,2.302-0.103,0.133-0.545-0.046-0.704,0.089-0.957,0.808-1.362,2.042-2.463,2.714-0.201,0.123-0.553-0.045-0.747,0.084-0.646,0.431-1.013,1.072-1.655,1.519-0.329,0.229-0.729-0.096-0.697-0.352,0.245-1.947,0.898-3.734,0.323-5.61,2.077-2.52,4.594-4.469,6.4-7.2,0.015-2.166,0.707-4.312,0.594-6.389-0.01-0.193-0.298-0.926-0.424-1.273-0.312-0.854,0.594-1.92-0.25-2.644-1.404-1.203-2.696-0.327-3.52,1.106-1.838,0.39-3.904,1.083-5.482-0.151-1.007-0.787-1.585-1.693-2.384-2.749-0.985-1.302-0.65-2.738-0.58-4.302,0.006-0.128-0.309-0.264-0.309-0.398,0.001-0.135,0.221-0.266,0.355-0.4-0.706-0.626-0.981-1.684-2-2,0.305-1.092-0.371-1.976-1.242-2.278-1.995-0.691-3.672,1.221-5.564,1.294-0.514,0.019-0.981-1.019-1.63-1.344-0.432-0.216-1.136-0.249-1.498,0.017-0.688,0.504-1.277,0.618-2.035,0.823-1.617,0.436-2.895,1.53-4.375,2.385-1.485,0.857-2.44,2.294-3.52,3.614-0.941,1.152-1.077,3.566,0.343,4.066,1.843,0.65,3.147-2.053,5.113-1.727,0.312,0.051,0.518,0.362,0.408,0.75,0.389,0.109,0.607-0.12,0.8-0.4,0.858,1.019,2.022,1.356,2.96,2.229,0.97,0.904,2.716,0.486,3.731,1.483,1.529,1.502,0.97,4.183,2.909,5.488-0.586,1.313-1.193,2.59-1.528,4.017-0.282,1.206,0.712,2.403,1.923,2.312,1.258-0.094,1.52-0.853,2.005-1.929,0.267,0.267,0.736,0.564,0.695,0.78-0.457,2.387-1.484,4.38-1.942,6.811-0.059,0.317-0.364,0.519-0.753,0.409-0.468,4.149-4.52,6.543-7.065,9.708-0.403,0.502-0.407,1.751,0.002,2.154,1.403,1.387,3.363-0.159,5.063-0.662,0.213-1.206,1.072-2.148,2.404-2.092,0.256,0.01,0.491-0.532,0.815-0.662,0.348-0.138,0.85,0.086,1.136-0.112,1.729-1.195,3.137-2.301,4.875-3.49,0.192-0.131,0.536,0.028,0.752-0.08,0.325-0.162,0.512-0.549,0.835-0.734,0.348-0.2,0.59,0.09,0.783,0.37-0.646,0.349-0.65,1.306-1.232,1.508-0.775,0.268-1.336,0.781-2.01,1.228-0.292,0.193-0.951-0.055-1.055,0.124-0.598,1.028-1.782,1.466-2.492,2.349z" diff --git a/tests/benchmark_png.nim b/tests/benchmark_png.nim index d2e0aa9..eb0b120 100644 --- a/tests/benchmark_png.nim +++ b/tests/benchmark_png.nim @@ -1,5 +1,5 @@ -import pixie/fileformats/png, stb_image/read as stbi, stb_image/write as stbr, - benchy, nimPNG +import benchy, nimPNG, pixie/fileformats/png, stb_image/read as stbi, + stb_image/write as stbr let data = readFile("tests/images/png/lenna.png") diff --git a/tests/benchmark_svg.nim b/tests/benchmark_svg.nim index cf6cf74..e36e3a6 100644 --- a/tests/benchmark_svg.nim +++ b/tests/benchmark_svg.nim @@ -1,4 +1,4 @@ -import pixie/fileformats/svg, benchy +import benchy, pixie/fileformats/svg let data = readFile("tests/images/svg/Ghostscript_Tiger.svg") diff --git a/tests/fuzz_jpg.nim b/tests/fuzz_jpg.nim index 015f97e..ea366e7 100644 --- a/tests/fuzz_jpg.nim +++ b/tests/fuzz_jpg.nim @@ -1,4 +1,4 @@ -import random, strformat, pixie/fileformats/jpg, pixie/common +import pixie/common, pixie/fileformats/jpg, random, strformat randomize() diff --git a/tests/fuzz_paths.nim b/tests/fuzz_paths.nim index a296670..8237878 100644 --- a/tests/fuzz_paths.nim +++ b/tests/fuzz_paths.nim @@ -1,4 +1,4 @@ -import random, strformat, pixie/paths, pixie/common, strformat +import pixie/common, pixie/paths, random, strformat randomize() diff --git a/tests/fuzz_png.nim b/tests/fuzz_png.nim index 89fb4a8..631d376 100644 --- a/tests/fuzz_png.nim +++ b/tests/fuzz_png.nim @@ -1,4 +1,4 @@ -import random, strformat, pixie/fileformats/png, pixie/common, pngsuite +import pixie/common, pixie/fileformats/png, pngsuite, random, strformat randomize() diff --git a/tests/fuzz_svg.nim b/tests/fuzz_svg.nim index faaf4ab..7356a18 100644 --- a/tests/fuzz_svg.nim +++ b/tests/fuzz_svg.nim @@ -1,4 +1,4 @@ -import random, strformat, pixie/fileformats/svg, pixie/common +import pixie/common, pixie/fileformats/svg, random, strformat randomize() diff --git a/tests/test_bmp.nim b/tests/test_bmp.nim index d02b36c..ba3c52c 100644 --- a/tests/test_bmp.nim +++ b/tests/test_bmp.nim @@ -1,4 +1,4 @@ -import pixie, pixie/fileformats/bmp, chroma +import chroma, pixie, pixie/fileformats/bmp block: var image = newImage(4, 2) @@ -32,5 +32,6 @@ block: block: for bits in [32, 24]: - var image = decodeBmp(readFile("tests/images/bmp/knight." & $bits & ".master.bmp")) + var image = decodeBmp(readFile("tests/images/bmp/knight." & $bits & + ".master.bmp")) writeFile("tests/images/bmp/knight." & $bits & ".bmp", encodeBmp(image)) diff --git a/tests/test_images.nim b/tests/test_images.nim index 0a56c7b..024637a 100644 --- a/tests/test_images.nim +++ b/tests/test_images.nim @@ -1,4 +1,4 @@ -import pixie, chroma, vmath +import chroma, pixie, vmath block: let image = newImage(10, 10) diff --git a/tests/test_paths.nim b/tests/test_paths.nim index ef28738..0ed6582 100644 --- a/tests/test_paths.nim +++ b/tests/test_paths.nim @@ -1,4 +1,4 @@ -import pixie, chroma, pixie/fileformats/png +import chroma, pixie, pixie/fileformats/png block: let pathStr = """ diff --git a/tests/test_png.nim b/tests/test_png.nim index 719bb8b..ffe3d37 100644 --- a/tests/test_png.nim +++ b/tests/test_png.nim @@ -1,4 +1,4 @@ -import pixie/fileformats/png, strformat, pngsuite, pixie/common +import pixie/common, pixie/fileformats/png, pngsuite, strformat for file in pngSuiteFiles: let diff --git a/tests/test_svg.nim b/tests/test_svg.nim index 2d09953..f4165f2 100644 --- a/tests/test_svg.nim +++ b/tests/test_svg.nim @@ -1,4 +1,4 @@ -import pixie/fileformats/svg, pixie, strformat +import pixie, pixie/fileformats/svg, strformat const files = [ "line01", diff --git a/tests/validate_png.nim b/tests/validate_png.nim index b7648ad..d5be59f 100644 --- a/tests/validate_png.nim +++ b/tests/validate_png.nim @@ -1,4 +1,4 @@ -import chroma, pixie/fileformats/png, stb_image/read as stbi, strformat, pngsuite +import chroma, pixie/fileformats/png, pngsuite, stb_image/read as stbi, strformat for file in pngSuiteFiles: let diff --git a/tools/gen_readme.nim b/tools/gen_readme.nim index 27cb9dc..c64a060 100644 --- a/tools/gen_readme.nim +++ b/tools/gen_readme.nim @@ -1,4 +1,4 @@ -import os, strutils, osproc, sequtils, algorithm +import algorithm, os, osproc, sequtils, strutils proc cutBetween(str, a, b: string): string = let @@ -30,7 +30,8 @@ for path in exampleFiles: md.add "```nim" md.add innerCode.strip() md.add "```" - md.add "![example output](" & path.replace(".nim", ".png").replace("\\", "/") & ")" + md.add "![example output](" & path.replace(".nim", ".png").replace("\\", + "/") & ")" md.add "" var readme = readFile("README.md") From ea3f924c81e4623245b76f0fc9cfecd4f42c3175 Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Sun, 14 Feb 2021 16:02:08 -0600 Subject: [PATCH 3/4] trapezoid experiment runs --- experiments/trapezoid.nim | 16 ++++++++-------- experiments/trapezoids/g.png | Bin 10980 -> 10985 bytes experiments/trapezoids/heart.png | Bin 4329 -> 4311 bytes experiments/trapezoids/l.png | Bin 7765 -> 7739 bytes pixie.nimble | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/experiments/trapezoid.nim b/experiments/trapezoid.nim index e99b589..1e13226 100644 --- a/experiments/trapezoid.nim +++ b/experiments/trapezoid.nim @@ -27,7 +27,7 @@ proc roundBy*(v: Vec2, n: float32): Vec2 {.inline.} = proc pathToTrapezoids(p: Path): seq[Trapezoid] = - var polygons = p.commands.commandsToPolygons() + var polygons = p.commandsToShapes() const q = 1/256.0 @@ -179,7 +179,7 @@ block: var image = newImage(200, 200) image.fill(rgba(255, 255, 255, 255)) - var p = newPath() + var p: Path p.moveTo(50, 50) p.lineTo(50, 150) p.lineTo(150, 150) @@ -189,7 +189,7 @@ block: var trapezoids = p.pathToTrapezoids() image.drawTrapezoids(trapezoids) - image.writeFile("trapezoids/rect.png") + image.writeFile("experiments/trapezoids/rect.png") block: # Rhombus @@ -197,7 +197,7 @@ block: var image = newImage(200, 200) image.fill(rgba(255, 255, 255, 255)) - var p = newPath() + var p: Path p.moveTo(100, 50) p.lineTo(150, 100) p.lineTo(100, 150) @@ -207,7 +207,7 @@ block: var trapezoids = p.pathToTrapezoids() image.drawTrapezoids(trapezoids) - image.writeFile("trapezoids/rhombus.png") + image.writeFile("experiments/trapezoids/rhombus.png") block: # heart @@ -223,7 +223,7 @@ block: var trapezoids = p.pathToTrapezoids() image.drawTrapezoids(trapezoids) - image.writeFile("trapezoids/heart.png") + image.writeFile("experiments/trapezoids/heart.png") block: # l @@ -240,7 +240,7 @@ block: var trapezoids = p.pathToTrapezoids() image.drawTrapezoids(trapezoids) - image.writeFile("trapezoids/l.png") + image.writeFile("experiments/trapezoids/l.png") block: # g @@ -257,4 +257,4 @@ block: var trapezoids = p.pathToTrapezoids() image.drawTrapezoids(trapezoids) - image.writeFile("trapezoids/g.png") + image.writeFile("experiments/trapezoids/g.png") diff --git a/experiments/trapezoids/g.png b/experiments/trapezoids/g.png index c63be150ddbe446da082cb13322f8ec66d45fc9b..fbfafa0f4f8d84a70aa006635672ead7ba412551 100644 GIT binary patch literal 10985 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 literal 10980 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 U-BWuBUa!JV8=4pt>p4aK2fi4R*Z=?k diff --git a/experiments/trapezoids/heart.png b/experiments/trapezoids/heart.png index 2a5f69b70fee238e797e618ec5a771d687748479..88c664995e57aaf65b778925dd0b67c1662cc57c 100644 GIT binary patch literal 4311 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 diff --git a/experiments/trapezoids/l.png b/experiments/trapezoids/l.png index 0507a7fbcdc53d64cccf19a86aec688d238cbf8f..1c88543bf21a24c25f94b2cc9e3826eb9a7147da 100644 GIT binary patch literal 7739 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 literal 7765 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 0.2.1" requires "zippy >= 0.3.5" requires "flatty >= 0.1.3" requires "nimsimd >= 1.0.0" -requires "bumpy >= 1.0.1" +requires "bumpy >= 1.0.2" From 331d5dfb376d4d10b032bc0767f919f708fbd2e7 Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Sun, 14 Feb 2021 16:22:45 -0600 Subject: [PATCH 4/4] f --- tests/test_bmp.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_bmp.nim b/tests/test_bmp.nim index ba3c52c..cc86f20 100644 --- a/tests/test_bmp.nim +++ b/tests/test_bmp.nim @@ -32,6 +32,6 @@ block: block: for bits in [32, 24]: - var image = decodeBmp(readFile("tests/images/bmp/knight." & $bits & - ".master.bmp")) + let image = + decodeBmp(readFile("tests/images/bmp/knight." & $bits & ".master.bmp")) writeFile("tests/images/bmp/knight." & $bits & ".bmp", encodeBmp(image))