diff --git a/src/main/java/com/github/fabriciofx/cactoos/pdf/content/PngImage.java b/src/main/java/com/github/fabriciofx/cactoos/pdf/content/PngImage.java index 19b1fdd..4633481 100644 --- a/src/main/java/com/github/fabriciofx/cactoos/pdf/content/PngImage.java +++ b/src/main/java/com/github/fabriciofx/cactoos/pdf/content/PngImage.java @@ -24,12 +24,16 @@ package com.github.fabriciofx.cactoos.pdf.content; import com.github.fabriciofx.cactoos.pdf.Count; -import com.github.fabriciofx.cactoos.pdf.Flow; import com.github.fabriciofx.cactoos.pdf.Object; +import com.github.fabriciofx.cactoos.pdf.png.Body; +import com.github.fabriciofx.cactoos.pdf.png.Header; +import com.github.fabriciofx.cactoos.pdf.png.Img; +import com.github.fabriciofx.cactoos.pdf.png.Palette; +import com.github.fabriciofx.cactoos.pdf.png.PngImg; +import com.github.fabriciofx.cactoos.pdf.png.SafePngImg; import java.io.ByteArrayOutputStream; import java.io.File; import java.nio.file.Files; -import java.util.Arrays; import org.cactoos.Bytes; import org.cactoos.text.FormattedText; import org.cactoos.text.Joined; @@ -38,7 +42,7 @@ public final class PngImage implements Object, Bytes { private final int number; private final int generation; - private final int paletteReference; + private final Count count; private final Bytes raw; public PngImage( @@ -48,7 +52,7 @@ public PngImage( this( count.increment(), 0, - count.increment(), + count, () -> Files.readAllBytes(new File(filename).toPath()) ); } @@ -56,12 +60,12 @@ public PngImage( public PngImage( final int number, final int generation, - final int paletteReference, + final Count count, final Bytes raw ) { this.number = number; this.generation = generation; - this.paletteReference = paletteReference; + this.count = count; this.raw = raw; } @@ -78,97 +82,11 @@ public String reference() { @Override public byte[] asBytes() throws Exception { - final Flow flow = new Flow(this.raw.asBytes()); - final byte[] signature = { - (byte) 137, 'P', 'N', 'G', '\r', '\n', 26, '\n' - }; - if (!Arrays.equals(signature, flow.asBytes(8))) { - throw new Exception("Not a PNG image file"); - } - flow.skip(4); - if (!flow.asString(4).equals("IHDR")) { - throw new Exception("Incorrect PNG image file"); - } - final int width = flow.asInt(); - final int height = flow.asInt(); - final int bitsPerComponent = flow.asByte(); - if (bitsPerComponent > 8) { - throw new Exception("16-bit depth in PNG file not supported"); - } - final int colorType = flow.asByte(); - final String colorSpace; - if (colorType == 0 || colorType == 4) { - colorSpace = "DeviceGray"; - } else if (colorType == 2 || colorType == 6) { - colorSpace = "DeviceRGB"; - } else if (colorType == 3) { - colorSpace = "Indexed"; - } else { - throw new Exception("Unknown color type"); - } - final int compressionMethod = flow.asByte(); - if (compressionMethod != 0) { - throw new Exception("Unknown compression method"); - } - final int filterMethod = flow.asByte(); - if (filterMethod != 0) { - throw new Exception("Unknown filter method"); - } - final int interlacing = flow.asByte(); - if (interlacing != 0) { - throw new Exception("Interlacing not supported"); - } - flow.skip(4); - final String dp = new FormattedText( - "/Predictor 15 /Colors %d /BitsPerComponent %d /Columns %d", - colorSpace.equals("DeviceRGB") ? 3 : 1, - bitsPerComponent, - width - ).asString(); - int n; - byte[] palette = new byte[0]; - byte[] trns = new byte[0]; - final ByteArrayOutputStream data = new ByteArrayOutputStream(); - do { - n = flow.asInt(); - final String type = flow.asString(4); - if (type.equals("PLTE")) { - palette = flow.asBytes(n); - flow.skip(4); - } else if (type.equals("tRNS")) { - final byte[] t = flow.asBytes(n); - if (colorType == 0) { - trns = new byte[]{(byte) new String(t).charAt(1)}; - } else if (colorType == 2) { - trns = new byte[]{ - (byte) new String(t).charAt(1), - (byte) new String(t).charAt(3), - (byte) new String(t).charAt(5) - }; - } else { - final int pos = new String(t).indexOf('0'); - if (pos != -1) { - trns = new byte[]{(byte) pos}; - } - } - flow.skip(4); - } else if (type.equals("IDAT")) { - data.write(flow.asBytes(n)); - flow.skip(4); - } else if (type.equals("IEND")) { - break; - } else { - flow.skip(n + 4); - } - } while (n > 0); - if (colorSpace.equals("Indexed") && palette.length == 0) { - throw new Exception("Missing palette in PNG file"); - } - if (colorType >= 4) { - System.out.println("alpha channel"); - } + final Img img = new SafePngImg(new PngImg(this.count, this.raw)); + final Header header = img.header(); + final Body body = img.body(); + final Palette palette = img.palette(); final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - // Write the image (data) object baos.write( new FormattedText( new Joined( @@ -178,39 +96,32 @@ public byte[] asBytes() throws Exception { "/Subtype /Image", "/Width %d", "/Height %d", - "/ColorSpace [/%s /DeviceRGB %d %d 0 R]", + "/ColorSpace [/%s /DeviceRGB %d %s]", "/BitsPerComponent %d", "/Filter /FlateDecode", - "/DecodeParms << %s >>", + "/DecodeParms << /Predictor 15 /Colors %d /BitsPerComponent %d /Columns %d >>", "/Mask [0 0]", "/Length %d >>", "stream\n" ), this.number, this.generation, - width, - height, - colorSpace, - palette.length / 3 - 1, - this.paletteReference, - bitsPerComponent, - dp, - data.toByteArray().length + header.width(), + header.height(), + header.color().space(), + palette.stream().length / 3 - 1, + palette.reference(), + header.depth(), + header.color().space().equals("DeviceRGB") ? 3 : 1, + header.depth(), + header.width(), + body.stream().length ).asString().getBytes() ); - baos.write(data.toByteArray()); + baos.write(body.stream()); baos.write("\nendstream\nendobj\n".getBytes()); // Write the palette object - baos.write( - new FormattedText( - "%d %d obj\n<< /Length %d >>\nstream\n", - this.paletteReference, - 0, - palette.length - ).asString().getBytes() - ); - baos.write(palette); - baos.write("\nendstream\nendobj\n".getBytes()); + baos.write(palette.asBytes()); return baos.toByteArray(); } } diff --git a/src/main/java/com/github/fabriciofx/cactoos/pdf/png/Body.java b/src/main/java/com/github/fabriciofx/cactoos/pdf/png/Body.java new file mode 100644 index 0000000..644b1c9 --- /dev/null +++ b/src/main/java/com/github/fabriciofx/cactoos/pdf/png/Body.java @@ -0,0 +1,6 @@ +package com.github.fabriciofx.cactoos.pdf.png; + +import com.github.fabriciofx.cactoos.pdf.Content; + +public interface Body extends Content { +} diff --git a/src/main/java/com/github/fabriciofx/cactoos/pdf/png/BytesAsInteger.java b/src/main/java/com/github/fabriciofx/cactoos/pdf/png/BytesAsInteger.java new file mode 100644 index 0000000..8885620 --- /dev/null +++ b/src/main/java/com/github/fabriciofx/cactoos/pdf/png/BytesAsInteger.java @@ -0,0 +1,25 @@ +package com.github.fabriciofx.cactoos.pdf.png; + +import org.cactoos.Bytes; +import org.cactoos.Scalar; + +public final class BytesAsInteger implements Scalar { + private final Bytes bytes; + + public BytesAsInteger(final byte[] bytes) { + this(() -> bytes); + } + + public BytesAsInteger(final Bytes bytes) { + this.bytes = bytes; + } + + @Override + public Integer value() throws Exception { + final byte[] raw = this.bytes.asBytes(); + return ((raw[0] & 0xFF) << 24) | + ((raw[1] & 0xFF) << 16) | + ((raw[2] & 0xFF) << 8) | + (raw[3] & 0xFF); + } +} diff --git a/src/main/java/com/github/fabriciofx/cactoos/pdf/png/Color.java b/src/main/java/com/github/fabriciofx/cactoos/pdf/png/Color.java new file mode 100644 index 0000000..ea6c709 --- /dev/null +++ b/src/main/java/com/github/fabriciofx/cactoos/pdf/png/Color.java @@ -0,0 +1,56 @@ +/* + * The MIT License (MIT) + * + * Copyright (C) 2023 Fabrício Barros Cabral + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.fabriciofx.cactoos.pdf.png; + +public final class Color { + private int type; + + public Color(final int type) { + this.type = type; + } + + public int type() { + return this.type; + } + + public String space() { + final String space; + switch(this.type) { + case 0: + case 4: + space = "DeviceGray"; + break; + case 2: + case 6: + space = "DeviceRGB"; + break; + case 3: + space = "Indexed"; + break; + default: + space = "Unknown"; + } + return space; + } +} diff --git a/src/main/java/com/github/fabriciofx/cactoos/pdf/png/Header.java b/src/main/java/com/github/fabriciofx/cactoos/pdf/png/Header.java new file mode 100644 index 0000000..0540428 --- /dev/null +++ b/src/main/java/com/github/fabriciofx/cactoos/pdf/png/Header.java @@ -0,0 +1,64 @@ +/* + * The MIT License (MIT) + * + * Copyright (C) 2023 Fabrício Barros Cabral + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.fabriciofx.cactoos.pdf.png; + +import org.cactoos.Bytes; +import org.cactoos.Text; + +/** + * PNG Image Header + * + * PNG Image Header Structure: + * ---------------------------- + * + * Offset Length Field Name Description + * --------------------------------------------- + * 0 8 bytes Signature PNG file signature (137 80 78 71 13 10 26 10) + * 8 4 bytes Chunk Length Length of the chunk data (excluding length and type) + * 12 4 bytes Chunk Type Type of the chunk (e.g., IHDR for image header) + * 16 4 bytes Width Width of the image in pixels + * 20 4 bytes Height Height of the image in pixels + * 24 1 byte Bit Depth Number of bits per sample + * 25 1 byte Color Type Type of color encoding used + * 26 1 byte Compression Compression method used + * 27 1 byte Filter Method Filtering method used + * 28 1 byte Interlace Method Interlace method used + * + * Additional chunks (not included in the header): + * - Data chunks (IDAT) containing the image data + * - Palette chunks (PLTE) for indexed-color images + * - Transparency chunks (tRNS) for specifying transparency + * - Textual information chunks (tEXt, iTXt) for metadata + * - Other optional chunks for various purposes + */ +public interface Header extends Text, Bytes { + int length(); + int width(); + int height(); + int depth(); + Color color(); + int compression(); + int filter(); + int interlacing(); +} diff --git a/src/main/java/com/github/fabriciofx/cactoos/pdf/png/Img.java b/src/main/java/com/github/fabriciofx/cactoos/pdf/png/Img.java new file mode 100644 index 0000000..1ba9c6b --- /dev/null +++ b/src/main/java/com/github/fabriciofx/cactoos/pdf/png/Img.java @@ -0,0 +1,30 @@ +/* + * The MIT License (MIT) + * + * Copyright (C) 2023 Fabrício Barros Cabral + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.fabriciofx.cactoos.pdf.png; + +public interface Img { + Header header() throws Exception; + Body body() throws Exception; + Palette palette() throws Exception; +} diff --git a/src/main/java/com/github/fabriciofx/cactoos/pdf/png/Palette.java b/src/main/java/com/github/fabriciofx/cactoos/pdf/png/Palette.java new file mode 100644 index 0000000..97129ca --- /dev/null +++ b/src/main/java/com/github/fabriciofx/cactoos/pdf/png/Palette.java @@ -0,0 +1,29 @@ +/* + * The MIT License (MIT) + * + * Copyright (C) 2023 Fabrício Barros Cabral + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.fabriciofx.cactoos.pdf.png; + +import com.github.fabriciofx.cactoos.pdf.Content; + +public interface Palette extends Content { +} diff --git a/src/main/java/com/github/fabriciofx/cactoos/pdf/png/PngBody.java b/src/main/java/com/github/fabriciofx/cactoos/pdf/png/PngBody.java new file mode 100644 index 0000000..391d587 --- /dev/null +++ b/src/main/java/com/github/fabriciofx/cactoos/pdf/png/PngBody.java @@ -0,0 +1,109 @@ +/* + * The MIT License (MIT) + * + * Copyright (C) 2023 Fabrício Barros Cabral + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.fabriciofx.cactoos.pdf.png; + +import com.github.fabriciofx.cactoos.pdf.Count; +import com.github.fabriciofx.cactoos.pdf.Flow; +import java.io.ByteArrayOutputStream; +import org.cactoos.Scalar; +import org.cactoos.scalar.Sticky; +import org.cactoos.text.FormattedText; +import org.cactoos.text.UncheckedText; + +public final class PngBody implements Body { + private final int number; + private final int generation; + private final Scalar bytes; + + public PngBody(final Count count, final byte[] bytes) { + this( + count.increment(), + 0, + new Sticky<>( + () -> { + final Flow flow = new Flow(bytes); + final ByteArrayOutputStream body = new ByteArrayOutputStream(); + int len; + flow.skip(33); + do { + len = flow.asInt(); + final String type = flow.asString(4); + if (type.equals("IDAT")) { + body.write(flow.asBytes(len)); + flow.skip(4); + } else if (type.equals("IEND")) { + break; + } else { + flow.skip(len + 4); + } + } while (len > 0); + return body.toByteArray(); + } + ) + ); + } + + public PngBody( + final int number, + final int generation, + final Scalar bytes + ) { + this.number = number; + this.generation = generation; + this.bytes = bytes; + } + + @Override + public String reference() { + return new UncheckedText( + new FormattedText( + "%d %d R", + this.number, + this.generation + ) + ).asString(); + } + + @Override + public byte[] stream() throws Exception { + return this.bytes.value(); + } + + @Override + public byte[] asBytes() throws Exception { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final byte[] stream = this.stream(); + baos.write( + new FormattedText( + "%d %d obj\n<< /Length %d >>\nstream\n", + this.number, + this.generation, + stream.length + ).asString().getBytes() + ); + baos.write(stream); + baos.write("\nendstream\nendobj\n".getBytes()); + return baos.toByteArray(); + } +} diff --git a/src/main/java/com/github/fabriciofx/cactoos/pdf/png/PngHeader.java b/src/main/java/com/github/fabriciofx/cactoos/pdf/png/PngHeader.java new file mode 100644 index 0000000..e607e76 --- /dev/null +++ b/src/main/java/com/github/fabriciofx/cactoos/pdf/png/PngHeader.java @@ -0,0 +1,147 @@ +/* + * The MIT License (MIT) + * + * Copyright (C) 2023 Fabrício Barros Cabral + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.fabriciofx.cactoos.pdf.png; + +import java.util.Arrays; +import org.cactoos.scalar.Unchecked; +import org.cactoos.text.FormattedText; +import org.cactoos.text.Joined; + +/** + * PNG Image Header + * + * PNG Image Header Structure: + * ---------------------------- + * + * Offset Length Field Name Description + * --------------------------------------------- + * 0 8 bytes Signature PNG file signature (137 80 78 71 13 10 26 10) + * 8 4 bytes Chunk Length Length of the chunk data (excluding length and type) + * 12 4 bytes Chunk Type Type of the chunk (e.g., IHDR for image header) + * 16 4 bytes Width Width of the image in pixels + * 20 4 bytes Height Height of the image in pixels + * 24 1 byte Bit Depth Number of bits per sample + * 25 1 byte Color Type Type of color encoding used + * 26 1 byte Compression Compression method used + * 27 1 byte Filter Method Filtering method used + * 28 1 byte Interlace Method Interlace method used + * + * Additional chunks (not included in the header): + * - Data chunks (IDAT) containing the image data + * - Palette chunks (PLTE) for indexed-color images + * - Transparency chunks (tRNS) for specifying transparency + * - Textual information chunks (tEXt, iTXt) for metadata + * - Other optional chunks for various purposes + */ +public final class PngHeader implements Header { + private final byte[] bytes; + + public PngHeader(final byte[] bytes) { + this.bytes = Arrays.copyOfRange(bytes, 0, 33); + } + + @Override + public int length() { + return new Unchecked<>( + new BytesAsInteger( + Arrays.copyOfRange(this.bytes, 8, 12) + ) + ).value(); + } + + @Override + public int width() { + return new Unchecked<>( + new BytesAsInteger( + Arrays.copyOfRange(this.bytes, 16, 20) + ) + ).value(); + } + + @Override + public int height() { + return new Unchecked<>( + new BytesAsInteger( + Arrays.copyOfRange(this.bytes, 20, 24) + ) + ).value(); + } + + @Override + public int depth() { + return this.bytes[24]; + } + + @Override + public Color color() { + return new Color(this.bytes[25]); + } + + @Override + public int compression() { + return this.bytes[26]; + } + + @Override + public int filter() { + return this.bytes[27]; + } + + @Override + public int interlacing() { + return this.bytes[28]; + } + + @Override + public String asString() throws Exception { + return new FormattedText( + new Joined( + "\n", + "Length: %d", + "Width: %d", + "Height: %d", + "Depth: %d", + "ColorType: %d", + "ColorSpace: %s", + "Compression: %d", + "Filter: %d", + "Interlacing: %d" + ), + this.length(), + this.width(), + this.height(), + this.depth(), + this.color().type(), + this.color().space(), + this.compression(), + this.filter(), + this.interlacing() + ).asString(); + } + + @Override + public byte[] asBytes() throws Exception { + return this.bytes; + } +} diff --git a/src/main/java/com/github/fabriciofx/cactoos/pdf/png/PngImg.java b/src/main/java/com/github/fabriciofx/cactoos/pdf/png/PngImg.java new file mode 100644 index 0000000..b4793a5 --- /dev/null +++ b/src/main/java/com/github/fabriciofx/cactoos/pdf/png/PngImg.java @@ -0,0 +1,58 @@ +/* + * The MIT License (MIT) + * + * Copyright (C) 2023 Fabrício Barros Cabral + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.fabriciofx.cactoos.pdf.png; + +import com.github.fabriciofx.cactoos.pdf.Count; +import java.io.File; +import java.nio.file.Files; +import org.cactoos.Bytes; + +public final class PngImg implements Img { + private final Count count; + private final Bytes bytes; + + public PngImg(final Count count, final String filename) { + this(count, () -> Files.readAllBytes(new File(filename).toPath())); + } + + public PngImg(final Count count, final Bytes bytes) { + this.count = count; + this.bytes = bytes; + } + + @Override + public Header header() throws Exception { + return new PngHeader(this.bytes.asBytes()); + } + + @Override + public Body body() throws Exception { + return new PngBody(this.count, this.bytes.asBytes()); + } + + @Override + public Palette palette() throws Exception { + return new PngPalette(this.count, this.bytes.asBytes()); + } +} diff --git a/src/main/java/com/github/fabriciofx/cactoos/pdf/png/PngPalette.java b/src/main/java/com/github/fabriciofx/cactoos/pdf/png/PngPalette.java new file mode 100644 index 0000000..d596dad --- /dev/null +++ b/src/main/java/com/github/fabriciofx/cactoos/pdf/png/PngPalette.java @@ -0,0 +1,109 @@ +/* + * The MIT License (MIT) + * + * Copyright (C) 2023 Fabrício Barros Cabral + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.fabriciofx.cactoos.pdf.png; + +import com.github.fabriciofx.cactoos.pdf.Count; +import com.github.fabriciofx.cactoos.pdf.Flow; +import java.io.ByteArrayOutputStream; +import org.cactoos.Scalar; +import org.cactoos.scalar.Sticky; +import org.cactoos.text.FormattedText; +import org.cactoos.text.UncheckedText; + +public final class PngPalette implements Palette { + private final int number; + private final int generation; + private final Scalar bytes; + + public PngPalette(final Count count, final byte[] bytes) { + this( + count.increment(), + 0, + new Sticky<>( + () -> { + final Flow flow = new Flow(bytes); + int len; + final ByteArrayOutputStream palette = new ByteArrayOutputStream(); + flow.skip(33); + do { + len = flow.asInt(); + final String type = flow.asString(4); + if (type.equals("PLTE")) { + palette.write(flow.asBytes(len)); + flow.skip(4); + } else if (type.equals("IEND")) { + break; + } else { + flow.skip(len + 4); + } + } while (len > 0); + return palette.toByteArray(); + } + ) + ); + } + + public PngPalette( + final int number, + final int generation, + final Scalar bytes + ) { + this.number = number; + this.generation = generation; + this.bytes = bytes; + } + + @Override + public String reference() { + return new UncheckedText( + new FormattedText( + "%d %d R", + this.number, + this.generation + ) + ).asString(); + } + + @Override + public byte[] stream() throws Exception { + return this.bytes.value(); + } + + @Override + public byte[] asBytes() throws Exception { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final byte[] stream = this.stream(); + baos.write( + new FormattedText( + "%d %d obj\n<< /Length %d >>\nstream\n", + this.number, + this.generation, + stream.length + ).asString().getBytes() + ); + baos.write(stream); + baos.write("\nendstream\nendobj\n".getBytes()); + return baos.toByteArray(); + } +} diff --git a/src/main/java/com/github/fabriciofx/cactoos/pdf/png/SafePngImg.java b/src/main/java/com/github/fabriciofx/cactoos/pdf/png/SafePngImg.java new file mode 100644 index 0000000..365c5ec --- /dev/null +++ b/src/main/java/com/github/fabriciofx/cactoos/pdf/png/SafePngImg.java @@ -0,0 +1,83 @@ +/* + * The MIT License (MIT) + * + * Copyright (C) 2023 Fabrício Barros Cabral + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.fabriciofx.cactoos.pdf.png; + +import com.github.fabriciofx.cactoos.pdf.Flow; +import java.util.Arrays; + +public final class SafePngImg implements Img { + private static final byte[] SIGNATURE = { + (byte) 137, 'P', 'N', 'G', '\r', '\n', 26, '\n' + }; + private final PngImg origin; + + public SafePngImg(final PngImg img) { + this.origin = img; + } + + @Override + public Header header() throws Exception { + final Header header = this.origin.header(); + final Flow flow = new Flow(header.asBytes()); + if (!Arrays.equals(SafePngImg.SIGNATURE, flow.asBytes(8))) { + throw new Exception("Not a PNG image file"); + } + flow.skip(4); + if (!flow.asString(4).equals("IHDR")) { + throw new Exception("Incorrect PNG image file"); + } + if (header.depth() > 8) { + throw new Exception("16-bit depth in PNG file not supported"); + } + if (header.color().space().equals("Unknown")) { + throw new Exception("Unknown color type"); + } + if (header.compression() != 0) { + throw new Exception("Unknown compression method"); + } + if (header.filter() != 0) { + throw new Exception("Unknown filter method"); + } + if (header.interlacing() != 0) { + throw new Exception("Interlacing not supported"); + } + return this.origin.header(); + } + + @Override + public Body body() throws Exception { + this.header(); + return this.origin.body(); + } + + @Override + public Palette palette() throws Exception { + final Header header = this.header(); + if (header.color().space().equals("Indexed") + && this.origin.palette().asBytes().length == 0) { + throw new Exception("Missing palette in PNG file"); + } + return this.origin.palette(); + } +} diff --git a/src/main/java/com/github/fabriciofx/cactoos/pdf/png/package-info.java b/src/main/java/com/github/fabriciofx/cactoos/pdf/png/package-info.java new file mode 100644 index 0000000..72bc8bd --- /dev/null +++ b/src/main/java/com/github/fabriciofx/cactoos/pdf/png/package-info.java @@ -0,0 +1,30 @@ +/* + * The MIT License (MIT) + * + * Copyright (C) 2023 Fabrício Barros Cabral + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * PNG. + * + * @since 0.0.1 + */ +package com.github.fabriciofx.cactoos.pdf.png; diff --git a/src/test/java/com/github/fabriciofx/cactoos/pdf/png/PngImgTest.java b/src/test/java/com/github/fabriciofx/cactoos/pdf/png/PngImgTest.java new file mode 100644 index 0000000..f227562 --- /dev/null +++ b/src/test/java/com/github/fabriciofx/cactoos/pdf/png/PngImgTest.java @@ -0,0 +1,87 @@ +/* + * The MIT License (MIT) + * + * Copyright (C) 2023 Fabrício Barros Cabral + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.fabriciofx.cactoos.pdf.png; + +import com.github.fabriciofx.cactoos.pdf.count.ObjectCount; +import org.cactoos.text.Joined; +import org.junit.jupiter.api.Test; +import org.llorllale.cactoos.matchers.Assertion; +import org.llorllale.cactoos.matchers.IsNumber; +import org.llorllale.cactoos.matchers.IsText; + +/** + * Test case for {@link PngImg}. + * + * @since 0.0.1 + */ +final class PngImgTest { + @Test + void header() throws Exception { + new Assertion<>( + "Must represent a PNG header", + new PngImg( + new ObjectCount(), + "src/test/resources/image/logo.png" + ).header(), + new IsText( + new Joined( + "\n", + "Length: 13", + "Width: 104", + "Height: 71", + "Depth: 8", + "ColorType: 3", + "ColorSpace: Indexed", + "Compression: 0", + "Filter: 0", + "Interlacing: 0" + ) + ) + ).affirm(); + } + + @Test + void body() throws Exception { + new Assertion<>( + "Must represent a PNG palette", + new PngImg( + new ObjectCount(), + "src/test/resources/image/logo.png" + ).body().stream().length, + new IsNumber(2086) + ).affirm(); + } + + @Test + void palette() throws Exception { + new Assertion<>( + "Must represent a PNG palette", + new PngImg( + new ObjectCount(), + "src/test/resources/image/logo.png" + ).palette().stream().length, + new IsNumber(192) + ).affirm(); + } +} diff --git a/src/test/java/com/github/fabriciofx/cactoos/pdf/png/package-info.java b/src/test/java/com/github/fabriciofx/cactoos/pdf/png/package-info.java new file mode 100644 index 0000000..21176cc --- /dev/null +++ b/src/test/java/com/github/fabriciofx/cactoos/pdf/png/package-info.java @@ -0,0 +1,30 @@ +/* + * The MIT License (MIT) + * + * Copyright (C) 2023 Fabrício Barros Cabral + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * Tests for PNG. + * + * @since 0.0.1 + */ +package com.github.fabriciofx.cactoos.pdf.png;