diff --git a/src/pixie/images.nim b/src/pixie/images.nim index eef3c143..55db070e 100644 --- a/src/pixie/images.nim +++ b/src/pixie/images.nim @@ -129,6 +129,12 @@ proc subImage*(image: Image, x, y, w, h: int): Image {.raises: [PixieError].} = w * 4 ) +proc subImage*(image: Image, rect: Rect): Image {.raises: [PixieError].} = + ## Gets a sub image from this image via rectangle. + ## Rectangle is snapped/expanded to whole pixels first. + let r = rect.snapToPixels() + image.subImage(r.x.int, r.y.int, r.w.int, r.h.int) + proc diff*(master, image: Image): (float32, Image) {.raises: [PixieError].} = ## Compares the parameters and returns a score and image of the difference. let @@ -751,5 +757,33 @@ proc superImage*(image: Image, x, y, w, h: int): Image {.raises: [PixieError].} result = newImage(w, h) result.draw(image, translate(vec2(-x.float32, -y.float32)), OverwriteBlend) +proc opaqueBounds*(image: Image): Rect = + ## Returns the bounds of opaque pixels. + ## Some images have transparency around them, use this to find just the + ## visible part of the image and then use subImage to cut it out. + ## Returns zero rect if whole image is transparent. + ## Returns just the size of the image if no edge is transparent. + var + xMin = image.width + xMax = 0 + yMin = image.height + yMax = 0 + # Find the trim coordinates. + for y in 0 ..< image.height: + for x in 0 ..< image.width: + if image.unsafe[x, y].a != 0: + xMin = min(xMin, x) + xMax = max(xMax, x + 1) + yMin = min(yMin, y) + yMax = max(yMax, y + 1) + if xMax <= xMin or yMax <= yMin: + return rect(0, 0, 0, 0) + rect( + xMin.float32, + yMin.float32, + (xMax - xMin).float32, + (yMax - yMin).float32 + ) + when defined(release): {.pop.} diff --git a/tests/images/opaqueBounds.png b/tests/images/opaqueBounds.png new file mode 100644 index 00000000..6f70c35b Binary files /dev/null and b/tests/images/opaqueBounds.png differ diff --git a/tests/test_images.nim b/tests/test_images.nim index 70756cde..50ac13e3 100644 --- a/tests/test_images.nim +++ b/tests/test_images.nim @@ -231,3 +231,30 @@ block: let image = newImage(100, 100) image.fill("white") doAssert image[10, 10] == rgba(255, 255, 255, 255) + +block: + # opaqueBounds of fully transparent image. + let image = newImage(100, 100) + doAssert image.opaqueBounds() == rect(0, 0, 0, 0) + +block: + # opaqueBounds of fully opaque image. + let image = newImage(100, 100) + image.fill(rgbx(255, 255, 255, 255)) + doAssert image.opaqueBounds() == rect(0.0, 0.0, 100.0, 100.0) + +block: + let image = newImage(160, 160) + image.fillPath( + """ + M 20 20 + L 140 20 + L 80 140 + z + """, + parseHtmlColor("#FC427B").rgba, + scale(vec2(0.3, 0.3)) + ) + let rect = image.opaqueBounds() + let trimmedImage = image.subImage(rect) + trimmedImage.xray("tests/images/opaqueBounds.png")