diff --git a/src/pixie/images.nim b/src/pixie/images.nim index d99d2cf..2bbb44c 100644 --- a/src/pixie/images.nim +++ b/src/pixie/images.nim @@ -142,36 +142,36 @@ proc subImage*(image: Image, x, y, w, h: int): Image = w * 4 ) -proc minifyBy2*(image: Image): Image = +proc minifyBy2*(image: Image, power = 1): Image = ## Scales the image down by an integer scale. - result = newImage(image.width div 2, image.height div 2) - for y in 0 ..< result.height: - for x in 0 ..< result.width: - var color = - image.getRgbaUnsafe(x * 2 + 0, y * 2 + 0).color / 4.0 + - image.getRgbaUnsafe(x * 2 + 1, y * 2 + 0).color / 4.0 + - image.getRgbaUnsafe(x * 2 + 1, y * 2 + 1).color / 4.0 + - image.getRgbaUnsafe(x * 2 + 0, y * 2 + 1).color / 4.0 - result.setRgbaUnsafe(x, y, color.rgba) + if power < 0: + raise newException(PixieError, "Cannot minifyBy2 with negative power") + if power == 0: + return image.copy() -proc minifyBy2*(image: Image, scale2x: int): Image = - ## Scales the image down by an integer scale. - result = image - for i in 1 ..< scale2x: - result = result.minifyBy2() + for i in 1 .. power: + result = newImage(image.width div 2, image.height div 2) + for y in 0 ..< result.height: + for x in 0 ..< result.width: + var color = + image.getRgbaUnsafe(x * 2 + 0, y * 2 + 0).color / 4.0 + + image.getRgbaUnsafe(x * 2 + 1, y * 2 + 0).color / 4.0 + + image.getRgbaUnsafe(x * 2 + 1, y * 2 + 1).color / 4.0 + + image.getRgbaUnsafe(x * 2 + 0, y * 2 + 1).color / 4.0 + result.setRgbaUnsafe(x, y, color.rgba) -proc magnifyBy2*(image: Image, scale2x: int): Image = - ## Scales image image up by an integer scale. - let scale = 2 ^ scale2x +proc magnifyBy2*(image: Image, power = 1): Image = + ## Scales image image up by 2 ^ power. + if power < 0: + raise newException(PixieError, "Cannot magnifyBy2 with negative power") + + let scale = 2 ^ power result = newImage(image.width * scale, image.height * scale) for y in 0 ..< result.height: for x in 0 ..< result.width: var rgba = image.getRgbaUnsafe(x div scale, y div scale) result.setRgbaUnsafe(x, y, rgba) -proc magnifyBy2*(image: Image): Image = - image.magnifyBy2(2) - proc toPremultipliedAlpha*(image: Image) = ## Converts an image to premultiplied alpha from straight alpha. var i: int @@ -492,15 +492,18 @@ proc draw*(a, b: Image, pos = vec2(0, 0), blendMode = bmNormal) {.inline.} = a.draw(b, translate(pos), blendMode) proc resize*(srcImage: Image, width, height: int): Image = - result = newImage(width, height) - result.draw( - srcImage, - scale(vec2( - (width + 1).float / srcImage.width.float, - (height + 1).float / srcImage.height.float - )), - bmOverwrite - ) + if width == srcImage.width and height == srcImage.height: + result = srcImage.copy() + else: + result = newImage(width, height) + result.draw( + srcImage, + scale(vec2( + width.float32 / srcImage.width.float32, + height.float32 / srcImage.height.float32 + )), + bmOverwrite + ) proc shift*(image: Image, offset: Vec2) = ## Shifts the image by offset. @@ -536,12 +539,15 @@ proc shadow*( mask: Image, offset: Vec2, spread, blur: float32, color: ColorRGBA ): Image = ## Create a shadow of the image with the offset, spread and blur. + # TODO: copying is bad here due to this being slow already, + # we're doing it tho to avoid mutating param and returning new Image. + let copy = mask.copy() if offset != vec2(0, 0): - mask.shift(offset) + copy.shift(offset) if spread > 0: - mask.spread(spread) + copy.spread(spread) if blur > 0: - mask.blurAlpha(blur) + copy.blurAlpha(blur) result = newImage(mask.width, mask.height) result.fill(color) - result.draw(mask, blendMode = bmMask) + result.draw(copy, blendMode = bmMask) diff --git a/src/pixie/paths.nim b/src/pixie/paths.nim index e6b4bfd..0c4e1fe 100644 --- a/src/pixie/paths.nim +++ b/src/pixie/paths.nim @@ -744,17 +744,18 @@ proc quickSort(a: var seq[(float32, int16)], inl, inr: int) = quickSort(a, inl, r) quickSort(a, l, inr) -proc computeBounds(segments: seq[(Segment, int16)]): Rect = +proc computeBounds(seqs: varargs[seq[(Segment, int16)]]): Rect = var xMin = float32.high xMax = float32.low yMin = float32.high yMax = float32.low - for (segment, _) in segments: - xMin = min(xMin, min(segment.at.x, segment.to.x)) - xMax = max(xMax, max(segment.at.x, segment.to.x)) - yMin = min(yMin, min(segment.at.y, segment.to.y)) - yMax = max(yMax, max(segment.at.y, segment.to.y)) + for s in seqs: + for (segment, _) in s: + xMin = min(xMin, min(segment.at.x, segment.to.x)) + xMax = max(xMax, max(segment.at.x, segment.to.x)) + yMin = min(yMin, min(segment.at.y, segment.to.y)) + yMax = max(yMax, max(segment.at.y, segment.to.y)) xMin = floor(xMin) xMax = ceil(xMax) @@ -772,22 +773,28 @@ proc fillShapes( color: ColorRGBA, windingRule: WindingRule ) = - var sortedSegments: seq[(Segment, int16)] + var topHalf, bottomHalf, fullHeight: seq[(Segment, int16)] for shape in shapes: for segment in shape.segments: if segment.at.y == segment.to.y: # Skip horizontal continue + var + segment = segment + winding = 1.int16 if segment.at.y > segment.to.y: - var segment = segment swap(segment.at, segment.to) - sortedSegments.add((segment, -1.int16)) + winding = -1 + if ceil(segment.to.y).int < image.height div 2: + topHalf.add((segment, winding)) + elif segment.at.y.int >= image.height div 2: + bottomHalf.add((segment, winding)) else: - sortedSegments.add((segment, 1.int16)) + fullHeight.add((segment, winding)) # Figure out the total bounds of all the shapes, # rasterize only within the total bounds let - bounds = computeBounds(sortedSegments) + bounds = computeBounds(topHalf, bottomHalf, fullHeight) startX = max(0, bounds.x.int) startY = max(0, bounds.y.int) stopY = min(image.height, (bounds.y + bounds.h).int) @@ -800,30 +807,44 @@ proc fillShapes( initialOffset = offset / 2 var - hits = newSeq[(float32, int16)](4) 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) - # Do scanlines for this row - for m in 0 ..< quality: - let - yLine = y.float32 + initialOffset + offset * m.float32 + ep - scanline = Line(a: vec2(0, yLine), b: vec2(1000, yLine)) - numHits = 0 - for (segment, winding) in sortedSegments: - if segment.at.y > yLine or segment.to.y < y.float32: - continue + proc intersects( + scanline: Line, + segment: Segment, + winding: int16, + hits: var seq[(float32, int16)], + numHits: var int + ) {.inline.} = + if segment.at.y <= scanline.a.y and segment.to.y >= scanline.a.y: var at: Vec2 if scanline.intersects(segment, at):# and segment.to != at: if numHits == hits.len: hits.setLen(hits.len * 2) - hits[numHits] = (at.x.clamp(0, image.width.float32), winding) + hits[numHits] = (at.x.clamp(0, scanline.b.x), winding) inc numHits + # Do scanlines for this row + for m in 0 ..< quality: + let + yLine = y.float32 + initialOffset + offset * m.float32 + ep + scanline = Line(a: vec2(0, yLine), b: vec2(image.width.float32, yLine)) + numHits = 0 + if y < image.height div 2: + for (segment, winding) in topHalf: + scanline.intersects(segment, winding, hits, numHits) + else: + for (segment, winding) in bottomHalf: + scanline.intersects(segment, winding, hits, numHits) + for (segment, winding) in fullHeight: + scanline.intersects(segment, winding, hits, numHits) + quickSort(hits, 0, numHits - 1) proc shouldFill(windingRule: WindingRule, count: int): bool {.inline.} = @@ -841,7 +862,7 @@ proc fillShapes( var fillStart = x.int - leftCover = if at.int - x.int > 0: ceil(x) - x else: at - x + leftCover = if at.int - x.int > 0: trunc(x) + 1 - x else: at - x if leftCover != 0: inc fillStart if shouldFill(windingRule, count): diff --git a/tests/images/svg/Ghostscript_Tiger.png b/tests/images/svg/Ghostscript_Tiger.png index fa713fe..8b5ea58 100644 Binary files a/tests/images/svg/Ghostscript_Tiger.png and b/tests/images/svg/Ghostscript_Tiger.png differ