I want to split a PNG file into 4 more PNGs, 3 for RGB and one alpha. When I input a PNG, the alpha channel is output correctly, but the RGB channels output nothing. If I input a JPG the RGB channels output correctly, but alpha fails because there is no alpha channel to get.
I've tried using multiple images all of which were either PNG or JPG and they all fail in the same way I've described.
val original = ImageIO.read(File("images/input.png"))
val alpha = BufferedImage(original.width, original.height, original.type)
val red = BufferedImage(original.width, original.height, original.type)
val green = BufferedImage(original.width, original.height, original.type)
val blue = BufferedImage(original.width, original.height, original.type)
for (y in 0 until original.height) {
for (x in 0 until original.width) {
val color = original.getRGB(x,y)
val a = color and 0xff000000.toInt()
val r = color and 0x00ff0000
val g = color and 0x0000ff00
val b = color and 0x000000ff
alpha.setRGB(x,y,a)
red.setRGB(x,y,r)
green.setRGB(x,y,g)
blue.setRGB(x,y,b)
}
}
ImageIO.write(alpha,"png", File("images/alpha.png"))
ImageIO.write(red,"png", File("images/red.png"))
ImageIO.write(green,"png", File("images/green.png"))
ImageIO.write(blue,"png", File("images/blue.png"))
I expect to receive 4 outputs with their respective channels alone, but I receive only an alpha channel with a PNG and no alpha channel with a JPG.
The reason your code don't work as intended, is partly because you create new BufferedImage
s based on the original type:
BufferedImage(original.width, original.height, original.type)
The original type will vary based on the input image. For JPEG, it will typically be TYPE_3BYTE_BGR
(which has no alpha). For PNG it depends on the type or PNG (grayscale, palette, true color, with or without alpha, etc). Your input PNG seems to be true color with alpha, probably resulting in TYPE_4BYTE_ABGR
or TYPE_INT_ARGB
.
Instead, you can either create 4 grayscale images (just "levels") using TYPE_BYTE_GRAY
, or as you do now, 4 ARGB ones using TYPE_INT_ARGB
(sorry if my Kotlin syntax is off, I mostly program Java these days):
BufferedImage(original.width, original.height, BufferedImage.TYPE_INT_ARGB)
Next, as @AlexanderEgger already pointed out, the set/getRGB
methods operates in packed ARGB format, so you need to make sure the colors are opaque (and
ing with just the color mask, will result in fully transparent color):
val a = color and 0xff000000.toInt()
val r = (color and 0x00ff0000) or 0xff000000.toInt()
val g = (color and 0x0000ff00) or 0xff000000.toInt()
val b = (color and 0x000000ff) or 0xff000000.toInt()
Or, if you like the levels approach, you do as @MarkSetchell suggests, and create all gray images (again, Kotlin syntax may be a little off):
BufferedImage(original.width, original.height, BufferedImage.TYPE_BYTE_GRAY)
...
val a = (color >> 24) and 0xff
val r = (color >> 16) and 0xff
val g = (color >> 8) and 0xff
val b = color and 0xff
val aa = 0xff000000.toInt() or (a << 16) or (a << 8) or a
val rr = 0xff000000.toInt() or (r << 16) or (r << 8) or r
val gg = 0xff000000.toInt() or (g << 16) or (g << 8) or g
val bb = 0xff000000.toInt() or (b << 16) or (b << 8) or b
(and pass aa
, rr
, gg
and bb
to setRGB
).