commit 588fd3394b8a8a9d428f306f97608e3487e8c1a3 Author: Jörg Prante Date: Sat Jan 20 23:26:37 2018 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dc122d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +/data +/work +/logs +/.idea +/target +.DS_Store +/.settings +/.classpath +/.project +/.gradle +/build +*~ +/*.iml \ No newline at end of file diff --git a/CREDITS.txt b/CREDITS.txt new file mode 100644 index 0000000..6e66a0c --- /dev/null +++ b/CREDITS.txt @@ -0,0 +1,5 @@ +This is a Java 8 version of the project + +https://github.com/gredler/jdk9-png-writer-backport + +by Daniel Gredler \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..b40a0f4 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,347 @@ +The GNU General Public License (GPL) + +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public License is intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. This General Public License applies to +most of the Free Software Foundation's software and to any other program whose +authors commit to using it. (Some other Free Software Foundation software is +covered by the GNU Library General Public License instead.) You can apply it to +your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the freedom to +distribute copies of free software (and charge for this service if you wish), +that you receive source code or can get it if you want it, that you can change +the software or use pieces of it in new free programs; and that you know you +can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to deny +you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of the +software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for +a fee, you must give the recipients all the rights that you have. You must +make sure that they, too, receive or can get the source code. And you must +show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) +offer you this license which gives you legal permission to copy, distribute +and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that +everyone understands that there is no warranty for this free software. If the +software is modified by someone else and passed on, we want its recipients to +know that what they have is not the original, so that any problems introduced +by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that redistributors of a free program will +individually obtain patent licenses, in effect making the program proprietary. +To prevent this, we have made it clear that any patent must be licensed for +everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification +follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice +placed by the copyright holder saying it may be distributed under the terms of +this General Public License. The "Program", below, refers to any such program +or work, and a "work based on the Program" means either the Program or any +derivative work under copyright law: that is to say, a work containing the +Program or a portion of it, either verbatim or with modifications and/or +translated into another language. (Hereinafter, translation is included +without limitation in the term "modification".) Each licensee is addressed as +"you". + +Activities other than copying, distribution and modification are not covered by +this License; they are outside its scope. The act of running the Program is +not restricted, and the output from the Program is covered only if its contents +constitute a work based on the Program (independent of having been made by +running the Program). Whether that is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's source code as +you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and +disclaimer of warranty; keep intact all the notices that refer to this License +and to the absence of any warranty; and give any other recipients of the +Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you may +at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, thus +forming a work based on the Program, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all of +these conditions: + + a) You must cause the modified files to carry prominent notices stating + that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in whole or + in part contains or is derived from the Program or any part thereof, to be + licensed as a whole at no charge to all third parties under the terms of + this License. + + c) If the modified program normally reads commands interactively when run, + you must cause it, when started running for such interactive use in the + most ordinary way, to print or display an announcement including an + appropriate copyright notice and a notice that there is no warranty (or + else, saying that you provide a warranty) and that users may redistribute + the program under these conditions, and telling the user how to view a copy + of this License. (Exception: if the Program itself is interactive but does + not normally print such an announcement, your work based on the Program is + not required to print an announcement.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Program, and can be reasonably +considered independent and separate works in themselves, then this License, and +its terms, do not apply to those sections when you distribute them as separate +works. But when you distribute the same sections as part of a whole which is a +work based on the Program, the distribution of the whole must be on the terms +of this License, whose permissions for other licensees extend to the entire +whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise the +right to control the distribution of derivative or collective works based on +the Program. + +In addition, mere aggregation of another work not based on the Program with the +Program (or with a work based on the Program) on a volume of a storage or +distribution medium does not bring the other work under the scope of this +License. + +3. You may copy and distribute the Program (or a work based on it, under +Section 2) in object code or executable form under the terms of Sections 1 and +2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable source + code, which must be distributed under the terms of Sections 1 and 2 above + on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three years, to + give any third party, for a charge no more than your cost of physically + performing source distribution, a complete machine-readable copy of the + corresponding source code, to be distributed under the terms of Sections 1 + and 2 above on a medium customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer to + distribute corresponding source code. (This alternative is allowed only + for noncommercial distribution and only if you received the program in + object code or executable form with such an offer, in accord with + Subsection b above.) + +The source code for a work means the preferred form of the work for making +modifications to it. For an executable work, complete source code means all +the source code for all modules it contains, plus any associated interface +definition files, plus the scripts used to control compilation and installation +of the executable. However, as a special exception, the source code +distributed need not include anything that is normally distributed (in either +source or binary form) with the major components (compiler, kernel, and so on) +of the operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the source +code from the same place counts as distribution of the source code, even though +third parties are not compelled to copy the source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as +expressly provided under this License. Any attempt otherwise to copy, modify, +sublicense or distribute the Program is void, and will automatically terminate +your rights under this License. However, parties who have received copies, or +rights, from you under this License will not have their licenses terminated so +long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. +However, nothing else grants you permission to modify or distribute the Program +or its derivative works. These actions are prohibited by law if you do not +accept this License. Therefore, by modifying or distributing the Program (or +any work based on the Program), you indicate your acceptance of this License to +do so, and all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), +the recipient automatically receives a license from the original licensor to +copy, distribute or modify the Program subject to these terms and conditions. +You may not impose any further restrictions on the recipients' exercise of the +rights granted herein. You are not responsible for enforcing compliance by +third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), conditions +are imposed on you (whether by court order, agreement or otherwise) that +contradict the conditions of this License, they do not excuse you from the +conditions of this License. If you cannot distribute so as to satisfy +simultaneously your obligations under this License and any other pertinent +obligations, then as a consequence you may not distribute the Program at all. +For example, if a patent license would not permit royalty-free redistribution +of the Program by all those who receive copies directly or indirectly through +you, then the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply and +the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or +other property right claims or to contest validity of any such claims; this +section has the sole purpose of protecting the integrity of the free software +distribution system, which is implemented by public license practices. Many +people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose that +choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain +countries either by patents or by copyrighted interfaces, the original +copyright holder who places the Program under this License may add an explicit +geographical distribution limitation excluding those countries, so that +distribution is permitted only in or among countries not thus excluded. In +such case, this License incorporates the limitation as if written in the body +of this License. + +9. The Free Software Foundation may publish revised and/or new versions of the +General Public License from time to time. Such new versions will be similar in +spirit to the present version, but may differ in detail to address new problems +or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any later +version", you have the option of following the terms and conditions either of +that version or of any later version published by the Free Software Foundation. +If the Program does not specify a version number of this License, you may +choose any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs +whose distribution conditions are different, write to the author to ask for +permission. For software which is copyrighted by the Free Software Foundation, +write to the Free Software Foundation; we sometimes make exceptions for this. +Our decision will be guided by the two goals of preserving the free status of +all derivatives of our free software and of promoting the sharing and reuse of +software generally. + +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE +PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, +YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL +ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE +PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR +INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA +BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER +OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible +use to the public, the best way to achieve this is to make it free software +which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach +them to the start of each source file to most effectively convey the exclusion +of warranty; and each file should have at least the "copyright" line and a +pointer to where the full notice is found. + + One line to give the program's name and a brief idea of what it does. + + Copyright (C) + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., 59 + Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this when it +starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author Gnomovision comes + with ABSOLUTELY NO WARRANTY; for details type 'show w'. This is free + software, and you are welcome to redistribute it under certain conditions; + type 'show c' for details. + +The hypothetical commands 'show w' and 'show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may be +called something other than 'show w' and 'show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the program, if necessary. Here +is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + 'Gnomovision' (which makes passes at compilers) written by James Hacker. + + signature of Ty Coon, 1 April 1989 + + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General Public +License instead of this License. + + +"CLASSPATH" EXCEPTION TO THE GPL + +Certain source files distributed by Oracle America and/or its affiliates are +subject to the following clarification and special exception to the GPL, but +only where Oracle has expressly included in the particular source file's header +the words "Oracle designates this particular file as subject to the "Classpath" +exception as provided by Oracle in the LICENSE file that accompanied this code." + + Linking this library statically or dynamically with other modules is making + a combined work based on this library. Thus, the terms and conditions of + the GNU General Public License cover the whole combination. + + As a special exception, the copyright holders of this library give you + permission to link this library with independent modules to produce an + executable, regardless of the license terms of these independent modules, + and to copy and distribute the resulting executable under terms of your + choice, provided that you also meet, for each linked independent module, + the terms and conditions of the license of that module. An independent + module is a module which is not derived from or based on this library. If + you modify this library, you may extend this exception to your version of + the library, but you are not obligated to do so. If you do not wish to do + so, delete this exception statement from your version. diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..79391f9 --- /dev/null +++ b/build.gradle @@ -0,0 +1,51 @@ + +plugins { + id "io.codearte.nexus-staging" version "0.11.0" +} + +apply plugin: 'java' +apply plugin: 'maven' +apply plugin: 'signing' +apply plugin: "io.codearte.nexus-staging" + +configurations { + wagon +} + +dependencies { + testCompile "junit:junit:4.12" + wagon "org.apache.maven.wagon:wagon-ssh:3.0.0" +} + +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 + +[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' +tasks.withType(JavaCompile) { + options.compilerArgs << "-Xlint:all" +} + +task javadocJar(type: Jar, dependsOn: classes) { + from javadoc + into "build/tmp" + classifier 'javadoc' +} + +task sourcesJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + into "build/tmp" + classifier 'sources' +} + +artifacts { + archives javadocJar, sourcesJar +} + +if (project.hasProperty('signing.keyId')) { + signing { + sign configurations.archives + } +} + +apply from: 'gradle/ext.gradle' +apply from: 'gradle/publish.gradle' diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..75b39d6 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,4 @@ +group = org.xbib +name = graphics +version = 0.0.1 + diff --git a/gradle/ext.gradle b/gradle/ext.gradle new file mode 100644 index 0000000..567648e --- /dev/null +++ b/gradle/ext.gradle @@ -0,0 +1,8 @@ +ext { + user = 'xbib' + projectName = 'graphics' + projectDescription = 'More graphics classes for Java' + scmUrl = 'https://github.com/xbib/graphics' + scmConnection = 'scm:git:git://github.com/xbib/graphics.git' + scmDeveloperConnection = 'scm:git:git://github.com/xbib/graphics.git' +} diff --git a/gradle/publish.gradle b/gradle/publish.gradle new file mode 100644 index 0000000..dd4f6ee --- /dev/null +++ b/gradle/publish.gradle @@ -0,0 +1,70 @@ + +task xbibUpload(type: Upload, dependsOn: build) { + configuration = configurations.archives + uploadDescriptor = true + repositories { + if (project.hasProperty('xbibUsername')) { + mavenDeployer { + configuration = configurations.wagon + repository(url: uri('sftp://xbib.org/repository')) { + authentication(userName: xbibUsername, privateKey: xbibPrivateKey) + } + } + } + } +} + +task sonatypeUpload(type: Upload, dependsOn: build) { + configuration = configurations.archives + uploadDescriptor = true + repositories { + if (project.hasProperty('ossrhUsername')) { + mavenDeployer { + beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } + repository(url: uri(ossrhReleaseUrl)) { + authentication(userName: ossrhUsername, password: ossrhPassword) + } + snapshotRepository(url: uri(ossrhSnapshotUrl)) { + authentication(userName: ossrhUsername, password: ossrhPassword) + } + pom.project { + groupId project.group + artifactId project.name + version project.version + name project.name + description projectDescription + packaging 'jar' + inceptionYear '2018' + url scmUrl + organization { + name 'xbib' + url 'http://xbib.org' + } + developers { + developer { + id user + name 'Jörg Prante' + email 'joergprante@gmail.com' + url 'https://github.com/jprante' + } + } + scm { + url scmUrl + connection scmConnection + developerConnection scmDeveloperConnection + } + licenses { + license { + name 'The GNU General Public License (GPL)' + url 'http://www.gnu.org/licenses/old-licenses/gpl-2.0.html' + } + } + } + } + } + } +} + +nexusStaging { + packageGroup = "org.xbib" +} diff --git a/src/main/java/com/sun/imageio/plugins/png/PNGImageWriterBackport.java b/src/main/java/com/sun/imageio/plugins/png/PNGImageWriterBackport.java new file mode 100644 index 0000000..9e242a6 --- /dev/null +++ b/src/main/java/com/sun/imageio/plugins/png/PNGImageWriterBackport.java @@ -0,0 +1,1274 @@ +/* + * Copyright (c) 2000, 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.sun.imageio.plugins.png; + +import javax.imageio.IIOException; +import javax.imageio.IIOImage; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.stream.ImageOutputStreamImpl; +import java.awt.Rectangle; +import java.awt.image.IndexColorModel; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.awt.image.SampleModel; +import java.awt.image.WritableRaster; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Iterator; +import java.util.Locale; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; + +final class CRCBackport { + + private static final int[] crcTable = new int[256]; + + static { + // Initialize CRC table + for (int n = 0; n < 256; n++) { + int c = n; + for (int k = 0; k < 8; k++) { + if ((c & 1) == 1) { + c = 0xedb88320 ^ (c >>> 1); + } else { + c >>>= 1; + } + + crcTable[n] = c; + } + } + } + + private int crc = 0xffffffff; + + CRCBackport() { + } + + void reset() { + crc = 0xffffffff; + } + + void update(byte[] data, int off, int len) { + int c = crc; + for (int n = 0; n < len; n++) { + c = crcTable[(c ^ data[off + n]) & 0xff] ^ (c >>> 8); + } + crc = c; + } + + void update(int data) { + crc = crcTable[(crc ^ data) & 0xff] ^ (crc >>> 8); + } + + int getValue() { + return ~crc; + } +} + +final class ChunkStreamBackport extends ImageOutputStreamImpl { + + private final ImageOutputStream stream; + + private final long startPos; + + private final CRCBackport crc = new CRCBackport(); + + ChunkStreamBackport(int type, ImageOutputStream stream) throws IOException { + this.stream = stream; + this.startPos = stream.getStreamPosition(); + + stream.writeInt(-1); // length, will backpatch + writeInt(type); + } + + @Override + public int read() { + throw new UnsupportedOperationException("Method not available"); + } + + @Override + public int read(byte[] b, int off, int len) { + throw new UnsupportedOperationException("Method not available"); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + crc.update(b, off, len); + stream.write(b, off, len); + } + + @Override + public void write(int b) throws IOException { + crc.update(b); + stream.write(b); + } + + void finish() throws IOException { + // Write CRC + stream.writeInt(crc.getValue()); + + // Write length + long pos = stream.getStreamPosition(); + stream.seek(startPos); + stream.writeInt((int) (pos - startPos) - 12); + + // Return to end of chunk and flush to minimize buffering + stream.seek(pos); + stream.flushBefore(pos); + } + + @Override + protected void finalize() throws Throwable { + // Empty finalizer (for improved performance; no need to call + // super.finalize() in this case) + } +} + +/* + * Compress output and write as a series of 'IDAT' chunks of + * fixed length. + */ +final class IDATOutputStreamBackport extends ImageOutputStreamImpl { + + private static final byte[] chunkType = { + (byte) 'I', (byte) 'D', (byte) 'A', (byte) 'T' + }; + + private final ImageOutputStream stream; + private final int chunkLength; + private final CRCBackport crc = new CRCBackport(); + private final Deflater def; + private final byte[] buf = new byte[512]; + // reused 1 byte[] array: + private final byte[] wbuf1 = new byte[1]; + private long startPos; + private int bytesRemaining; + + IDATOutputStreamBackport(ImageOutputStream stream, int chunkLength, + int deflaterLevel) throws IOException { + this.stream = stream; + this.chunkLength = chunkLength; + this.def = new Deflater(deflaterLevel); + + startChunk(); + } + + private void startChunk() throws IOException { + crc.reset(); + this.startPos = stream.getStreamPosition(); + stream.writeInt(-1); // length, will backpatch + + crc.update(chunkType, 0, 4); + stream.write(chunkType, 0, 4); + + this.bytesRemaining = chunkLength; + } + + private void finishChunk() throws IOException { + // Write CRC + stream.writeInt(crc.getValue()); + + // Write length + long pos = stream.getStreamPosition(); + stream.seek(startPos); + stream.writeInt((int) (pos - startPos) - 12); + + // Return to end of chunk and flush to minimize buffering + stream.seek(pos); + try { + stream.flushBefore(pos); + } catch (IOException e) { + /* + * If flushBefore() fails we try to access startPos in finally + * block of write_IDAT(). We should update startPos to avoid + * IndexOutOfBoundException while seek() is happening. + */ + this.startPos = stream.getStreamPosition(); + throw e; + } + } + + @Override + public int read() { + throw new UnsupportedOperationException("Method not available"); + } + + @Override + public int read(byte[] b, int off, int len) { + throw new UnsupportedOperationException("Method not available"); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (len == 0) { + return; + } + + if (!def.finished()) { + def.setInput(b, off, len); + while (!def.needsInput()) { + deflate(); + } + } + } + + private void deflate() throws IOException { + int len = def.deflate(buf, 0, buf.length); + int off = 0; + + while (len > 0) { + if (bytesRemaining == 0) { + finishChunk(); + startChunk(); + } + + int nbytes = Math.min(len, bytesRemaining); + crc.update(buf, off, nbytes); + stream.write(buf, off, nbytes); + + off += nbytes; + len -= nbytes; + bytesRemaining -= nbytes; + } + } + + @Override + public void write(int b) throws IOException { + wbuf1[0] = (byte) b; + write(wbuf1, 0, 1); + } + + void finish() throws IOException { + try { + if (!def.finished()) { + def.finish(); + while (!def.finished()) { + deflate(); + } + } + finishChunk(); + } finally { + def.end(); + } + } + + @Override + protected void finalize() throws Throwable { + // Empty finalizer (for improved performance; no need to call + // super.finalize() in this case) + } +} + + +final class PNGImageWriteParamBackport extends ImageWriteParam { + + /** + * Default quality level = 0.5 ie medium compression + */ + private static final float DEFAULT_QUALITY = 0.5f; + + private static final String[] compressionNames = {"Deflate"}; + private static final float[] qualityVals = {0.00F, 0.30F, 0.75F, 1.00F}; + private static final String[] qualityDescs = { + "High compression", // 0.00 -> 0.30 + "Medium compression", // 0.30 -> 0.75 + "Low compression" // 0.75 -> 1.00 + }; + + PNGImageWriteParamBackport(Locale locale) { + super(); + this.canWriteProgressive = true; + this.locale = locale; + this.canWriteCompressed = true; + this.compressionTypes = compressionNames; + this.compressionType = compressionTypes[0]; + this.compressionMode = MODE_DEFAULT; + this.compressionQuality = DEFAULT_QUALITY; + } + + /** + * Removes any previous compression quality setting. + * The default implementation resets the compression quality + * to 0.5F. + * + * @throws IllegalStateException if the compression mode is not + * MODE_EXPLICIT. + */ + @Override + public void unsetCompression() { + super.unsetCompression(); + this.compressionType = compressionTypes[0]; + this.compressionQuality = DEFAULT_QUALITY; + } + + /** + * Returns true since the PNG plug-in only supports + * lossless compression. + * + * @return true. + */ + @Override + public boolean isCompressionLossless() { + return true; + } + + @Override + public String[] getCompressionQualityDescriptions() { + super.getCompressionQualityDescriptions(); + return qualityDescs.clone(); + } + + @Override + public float[] getCompressionQualityValues() { + super.getCompressionQualityValues(); + return qualityVals.clone(); + } +} + +/** + */ +public final class PNGImageWriterBackport extends ImageWriter { + + /** + * Default compression level = 4 ie medium compression + */ + private static final int DEFAULT_COMPRESSION_LEVEL = 4; + + private ImageOutputStream stream = null; + + private PNGMetadata metadata = null; + + // Factors from the ImageWriteParam + private int sourceXOffset = 0; + private int sourceYOffset = 0; + private int sourceWidth = 0; + private int sourceHeight = 0; + private int[] sourceBands = null; + private int periodX = 1; + private int periodY = 1; + + private int numBands; + private int bpp; + + private RowFilter rowFilter = new RowFilter(); + + // Per-band scaling tables + // + // After the first call to initializeScaleTables, either scale and scale0 + // will be valid, or scaleh and scalel will be valid, but not both. + // + // The tables will be designed for use with a set of input but depths + // given by sampleSize, and an output bit depth given by scalingBitDepth. + // + private int[] sampleSize = null; // Sample size per band, in bits + private int scalingBitDepth = -1; // Output bit depth of the scaling tables + + // Tables for 1, 2, 4, or 8 bit output + private byte[][] scale = null; // 8 bit table + private byte[] scale0 = null; // equivalent to scale[0] + + // Tables for 16 bit output + private byte[][] scaleh = null; // High bytes of output + private byte[][] scalel = null; // Low bytes of output + + private int totalPixels; // Total number of pixels to be written by write_IDAT + private int pixelsDone; // Running count of pixels written by write_IDAT + + PNGImageWriterBackport(ImageWriterSpi originatingProvider) { + super(originatingProvider); + } + + private static int chunkType(String typeString) { + char c0 = typeString.charAt(0); + char c1 = typeString.charAt(1); + char c2 = typeString.charAt(2); + char c3 = typeString.charAt(3); + + return (c0 << 24) | (c1 << 16) | (c2 << 8) | c3; + } + + @Override + public void setOutput(Object output) { + super.setOutput(output); + if (output != null) { + if (!(output instanceof ImageOutputStream)) { + throw new IllegalArgumentException("output not an ImageOutputStream!"); + } + this.stream = (ImageOutputStream) output; + } else { + this.stream = null; + } + } + + @Override + public ImageWriteParam getDefaultWriteParam() { + return new PNGImageWriteParamBackport(getLocale()); + } + + @Override + public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) { + return null; + } + + @Override + public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, + ImageWriteParam param) { + PNGMetadata m = new PNGMetadata(); + m.initialize(imageType, imageType.getSampleModel().getNumBands()); + return m; + } + + @Override + public IIOMetadata convertStreamMetadata(IIOMetadata inData, + ImageWriteParam param) { + return null; + } + + @Override + public IIOMetadata convertImageMetadata(IIOMetadata inData, + ImageTypeSpecifier imageType, + ImageWriteParam param) { + if (inData instanceof PNGMetadata) { + return (PNGMetadata) ((PNGMetadata) inData).clone(); + } else { + return new PNGMetadata(inData); + } + } + + private void write_magic() throws IOException { + // Write signature + byte[] magic = {(byte) 137, 80, 78, 71, 13, 10, 26, 10}; + stream.write(magic); + } + + private void write_IHDR() throws IOException { + // Write IHDR chunk + ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.IHDR_TYPE, stream); + cs.writeInt(metadata.IHDR_width); + cs.writeInt(metadata.IHDR_height); + cs.writeByte(metadata.IHDR_bitDepth); + cs.writeByte(metadata.IHDR_colorType); + if (metadata.IHDR_compressionMethod != 0) { + throw new IIOException( + "Only compression method 0 is defined in PNG 1.1"); + } + cs.writeByte(metadata.IHDR_compressionMethod); + if (metadata.IHDR_filterMethod != 0) { + throw new IIOException( + "Only filter method 0 is defined in PNG 1.1"); + } + cs.writeByte(metadata.IHDR_filterMethod); + if (metadata.IHDR_interlaceMethod < 0 || + metadata.IHDR_interlaceMethod > 1) { + throw new IIOException( + "Only interlace methods 0 (node) and 1 (adam7) are defined in PNG 1.1"); + } + cs.writeByte(metadata.IHDR_interlaceMethod); + cs.finish(); + } + + private void write_cHRM() throws IOException { + if (metadata.cHRM_present) { + ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.cHRM_TYPE, stream); + cs.writeInt(metadata.cHRM_whitePointX); + cs.writeInt(metadata.cHRM_whitePointY); + cs.writeInt(metadata.cHRM_redX); + cs.writeInt(metadata.cHRM_redY); + cs.writeInt(metadata.cHRM_greenX); + cs.writeInt(metadata.cHRM_greenY); + cs.writeInt(metadata.cHRM_blueX); + cs.writeInt(metadata.cHRM_blueY); + cs.finish(); + } + } + + private void write_gAMA() throws IOException { + if (metadata.gAMA_present) { + ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.gAMA_TYPE, stream); + cs.writeInt(metadata.gAMA_gamma); + cs.finish(); + } + } + + private void write_iCCP() throws IOException { + if (metadata.iCCP_present) { + ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.iCCP_TYPE, stream); + cs.writeBytes(metadata.iCCP_profileName); + cs.writeByte(0); // null terminator + + cs.writeByte(metadata.iCCP_compressionMethod); + cs.write(metadata.iCCP_compressedProfile); + cs.finish(); + } + } + + private void write_sBIT() throws IOException { + if (metadata.sBIT_present) { + ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.sBIT_TYPE, stream); + int colorType = metadata.IHDR_colorType; + if (metadata.sBIT_colorType != colorType) { + processWarningOccurred(0, + "sBIT metadata has wrong color type.\n" + + "The chunk will not be written."); + return; + } + + if (colorType == PNGImageReader.PNG_COLOR_GRAY || + colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) { + cs.writeByte(metadata.sBIT_grayBits); + } else if (colorType == PNGImageReader.PNG_COLOR_RGB || + colorType == PNGImageReader.PNG_COLOR_PALETTE || + colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) { + cs.writeByte(metadata.sBIT_redBits); + cs.writeByte(metadata.sBIT_greenBits); + cs.writeByte(metadata.sBIT_blueBits); + } + + if (colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA || + colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) { + cs.writeByte(metadata.sBIT_alphaBits); + } + cs.finish(); + } + } + + private void write_sRGB() throws IOException { + if (metadata.sRGB_present) { + ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.sRGB_TYPE, stream); + cs.writeByte(metadata.sRGB_renderingIntent); + cs.finish(); + } + } + + private void write_PLTE() throws IOException { + if (metadata.PLTE_present) { + if (metadata.IHDR_colorType == PNGImageReader.PNG_COLOR_GRAY || + metadata.IHDR_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) { + // PLTE cannot occur in a gray image + + processWarningOccurred(0, + "A PLTE chunk may not appear in a gray or gray alpha image.\n" + + "The chunk will not be written"); + return; + } + + ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.PLTE_TYPE, stream); + + int numEntries = metadata.PLTE_red.length; + byte[] palette = new byte[numEntries * 3]; + int index = 0; + for (int i = 0; i < numEntries; i++) { + palette[index++] = metadata.PLTE_red[i]; + palette[index++] = metadata.PLTE_green[i]; + palette[index++] = metadata.PLTE_blue[i]; + } + + cs.write(palette); + cs.finish(); + } + } + + private void write_hIST() throws IOException { + if (metadata.hIST_present) { + ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.hIST_TYPE, stream); + + if (!metadata.PLTE_present) { + throw new IIOException("hIST chunk without PLTE chunk!"); + } + + cs.writeChars(metadata.hIST_histogram, + 0, metadata.hIST_histogram.length); + cs.finish(); + } + } + + private void write_tRNS() throws IOException { + if (metadata.tRNS_present) { + ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.tRNS_TYPE, stream); + int colorType = metadata.IHDR_colorType; + int chunkType = metadata.tRNS_colorType; + + // Special case: image is RGB and chunk is Gray + // Promote chunk contents to RGB + int chunkRed = metadata.tRNS_red; + int chunkGreen = metadata.tRNS_green; + int chunkBlue = metadata.tRNS_blue; + if (colorType == PNGImageReader.PNG_COLOR_RGB && + chunkType == PNGImageReader.PNG_COLOR_GRAY) { + chunkType = colorType; + chunkRed = chunkGreen = chunkBlue = + metadata.tRNS_gray; + } + + if (chunkType != colorType) { + processWarningOccurred(0, + "tRNS metadata has incompatible color type.\n" + + "The chunk will not be written."); + return; + } + + if (colorType == PNGImageReader.PNG_COLOR_PALETTE) { + if (!metadata.PLTE_present) { + throw new IIOException("tRNS chunk without PLTE chunk!"); + } + cs.write(metadata.tRNS_alpha); + } else if (colorType == PNGImageReader.PNG_COLOR_GRAY) { + cs.writeShort(metadata.tRNS_gray); + } else if (colorType == PNGImageReader.PNG_COLOR_RGB) { + cs.writeShort(chunkRed); + cs.writeShort(chunkGreen); + cs.writeShort(chunkBlue); + } else { + throw new IIOException("tRNS chunk for color type 4 or 6!"); + } + cs.finish(); + } + } + + private void write_bKGD() throws IOException { + if (metadata.bKGD_present) { + ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.bKGD_TYPE, stream); + int colorType = metadata.IHDR_colorType & 0x3; + int chunkType = metadata.bKGD_colorType; + + // Special case: image is RGB(A) and chunk is Gray + // Promote chunk contents to RGB + int chunkRed = metadata.bKGD_red; + int chunkGreen = metadata.bKGD_red; + int chunkBlue = metadata.bKGD_red; + if (colorType == PNGImageReader.PNG_COLOR_RGB && + chunkType == PNGImageReader.PNG_COLOR_GRAY) { + // Make a gray bKGD chunk look like RGB + chunkType = colorType; + chunkRed = chunkGreen = chunkBlue = + metadata.bKGD_gray; + } + + // Ignore status of alpha in colorType + if (chunkType != colorType) { + processWarningOccurred(0, + "bKGD metadata has incompatible color type.\n" + + "The chunk will not be written."); + return; + } + + if (colorType == PNGImageReader.PNG_COLOR_PALETTE) { + cs.writeByte(metadata.bKGD_index); + } else if (colorType == PNGImageReader.PNG_COLOR_GRAY) { + cs.writeShort(metadata.bKGD_gray); + } else { // colorType == PNGImageReader.PNG_COLOR_RGB || + // colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA + cs.writeShort(chunkRed); + cs.writeShort(chunkGreen); + cs.writeShort(chunkBlue); + } + cs.finish(); + } + } + + private void write_pHYs() throws IOException { + if (metadata.pHYs_present) { + ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.pHYs_TYPE, stream); + cs.writeInt(metadata.pHYs_pixelsPerUnitXAxis); + cs.writeInt(metadata.pHYs_pixelsPerUnitYAxis); + cs.writeByte(metadata.pHYs_unitSpecifier); + cs.finish(); + } + } + + private void write_sPLT() throws IOException { + if (metadata.sPLT_present) { + ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.sPLT_TYPE, stream); + + cs.writeBytes(metadata.sPLT_paletteName); + cs.writeByte(0); // null terminator + + cs.writeByte(metadata.sPLT_sampleDepth); + int numEntries = metadata.sPLT_red.length; + + if (metadata.sPLT_sampleDepth == 8) { + for (int i = 0; i < numEntries; i++) { + cs.writeByte(metadata.sPLT_red[i]); + cs.writeByte(metadata.sPLT_green[i]); + cs.writeByte(metadata.sPLT_blue[i]); + cs.writeByte(metadata.sPLT_alpha[i]); + cs.writeShort(metadata.sPLT_frequency[i]); + } + } else { // sampleDepth == 16 + for (int i = 0; i < numEntries; i++) { + cs.writeShort(metadata.sPLT_red[i]); + cs.writeShort(metadata.sPLT_green[i]); + cs.writeShort(metadata.sPLT_blue[i]); + cs.writeShort(metadata.sPLT_alpha[i]); + cs.writeShort(metadata.sPLT_frequency[i]); + } + } + cs.finish(); + } + } + + private void write_tIME() throws IOException { + if (metadata.tIME_present) { + ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.tIME_TYPE, stream); + cs.writeShort(metadata.tIME_year); + cs.writeByte(metadata.tIME_month); + cs.writeByte(metadata.tIME_day); + cs.writeByte(metadata.tIME_hour); + cs.writeByte(metadata.tIME_minute); + cs.writeByte(metadata.tIME_second); + cs.finish(); + } + } + + private void write_tEXt() throws IOException { + Iterator keywordIter = metadata.tEXt_keyword.iterator(); + Iterator textIter = metadata.tEXt_text.iterator(); + + while (keywordIter.hasNext()) { + ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.tEXt_TYPE, stream); + String keyword = keywordIter.next(); + cs.writeBytes(keyword); + cs.writeByte(0); + + String text = textIter.next(); + cs.writeBytes(text); + cs.finish(); + } + } + + private byte[] deflate(byte[] b) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DeflaterOutputStream dos = new DeflaterOutputStream(baos); + dos.write(b); + dos.close(); + return baos.toByteArray(); + } + + private void write_iTXt() throws IOException { + Iterator keywordIter = metadata.iTXt_keyword.iterator(); + Iterator flagIter = metadata.iTXt_compressionFlag.iterator(); + Iterator methodIter = metadata.iTXt_compressionMethod.iterator(); + Iterator languageIter = metadata.iTXt_languageTag.iterator(); + Iterator translatedKeywordIter = + metadata.iTXt_translatedKeyword.iterator(); + Iterator textIter = metadata.iTXt_text.iterator(); + + while (keywordIter.hasNext()) { + ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.iTXt_TYPE, stream); + + cs.writeBytes(keywordIter.next()); + cs.writeByte(0); + + Boolean compressed = flagIter.next(); + cs.writeByte(compressed ? 1 : 0); + + cs.writeByte(methodIter.next()); + + cs.writeBytes(languageIter.next()); + cs.writeByte(0); + + + cs.write(translatedKeywordIter.next().getBytes("UTF8")); + cs.writeByte(0); + + String text = textIter.next(); + if (compressed) { + cs.write(deflate(text.getBytes("UTF8"))); + } else { + cs.write(text.getBytes("UTF8")); + } + cs.finish(); + } + } + + private void write_zTXt() throws IOException { + Iterator keywordIter = metadata.zTXt_keyword.iterator(); + Iterator methodIter = metadata.zTXt_compressionMethod.iterator(); + Iterator textIter = metadata.zTXt_text.iterator(); + + while (keywordIter.hasNext()) { + ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.zTXt_TYPE, stream); + String keyword = keywordIter.next(); + cs.writeBytes(keyword); + cs.writeByte(0); + + int compressionMethod = methodIter.next(); + cs.writeByte(compressionMethod); + + String text = textIter.next(); + cs.write(deflate(text.getBytes("ISO-8859-1"))); + cs.finish(); + } + } + + private void writeUnknownChunks() throws IOException { + Iterator typeIter = metadata.unknownChunkType.iterator(); + Iterator dataIter = metadata.unknownChunkData.iterator(); + + while (typeIter.hasNext() && dataIter.hasNext()) { + String type = typeIter.next(); + ChunkStreamBackport cs = new ChunkStreamBackport(chunkType(type), stream); + byte[] data = dataIter.next(); + cs.write(data); + cs.finish(); + } + } + + private void encodePass(ImageOutputStream os, + RenderedImage image, + int xOffset, int yOffset, + int xSkip, int ySkip) throws IOException { + int minX = sourceXOffset; + int minY = sourceYOffset; + int width = sourceWidth; + int height = sourceHeight; + + // Adjust offsets and skips based on source subsampling factors + xOffset *= periodX; + xSkip *= periodX; + yOffset *= periodY; + ySkip *= periodY; + + // Early exit if no data for this pass + int hpixels = (width - xOffset + xSkip - 1) / xSkip; + int vpixels = (height - yOffset + ySkip - 1) / ySkip; + if (hpixels == 0 || vpixels == 0) { + return; + } + + // Convert X offset and skip from pixels to samples + xOffset *= numBands; + xSkip *= numBands; + + // Create row buffers + int samplesPerByte = 8 / metadata.IHDR_bitDepth; + int numSamples = width * numBands; + int[] samples = new int[numSamples]; + + int bytesPerRow = hpixels * numBands; + if (metadata.IHDR_bitDepth < 8) { + bytesPerRow = (bytesPerRow + samplesPerByte - 1) / samplesPerByte; + } else if (metadata.IHDR_bitDepth == 16) { + bytesPerRow *= 2; + } + + IndexColorModel icm_gray_alpha = null; + if (metadata.IHDR_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA && + image.getColorModel() instanceof IndexColorModel) { + // reserve space for alpha samples + bytesPerRow *= 2; + + // will be used to calculate alpha value for the pixel + icm_gray_alpha = (IndexColorModel) image.getColorModel(); + } + + byte[] currRow = new byte[bytesPerRow + bpp]; + byte[] prevRow = new byte[bytesPerRow + bpp]; + byte[][] filteredRows = new byte[5][bytesPerRow + bpp]; + + int bitDepth = metadata.IHDR_bitDepth; + for (int row = minY + yOffset; row < minY + height; row += ySkip) { + Rectangle rect = new Rectangle(minX, row, width, 1); + Raster ras = image.getData(rect); + if (sourceBands != null) { + ras = ras.createChild(minX, row, width, 1, minX, row, + sourceBands); + } + + ras.getPixels(minX, row, width, 1, samples); + + if (image.getColorModel().isAlphaPremultiplied()) { + WritableRaster wr = ras.createCompatibleWritableRaster(); + wr.setPixels(wr.getMinX(), wr.getMinY(), + wr.getWidth(), wr.getHeight(), + samples); + + image.getColorModel().coerceData(wr, false); + wr.getPixels(wr.getMinX(), wr.getMinY(), + wr.getWidth(), wr.getHeight(), + samples); + } + + // Reorder palette data if necessary + int[] paletteOrder = metadata.PLTE_order; + if (paletteOrder != null) { + for (int i = 0; i < numSamples; i++) { + samples[i] = paletteOrder[samples[i]]; + } + } + + int count = bpp; // leave first 'bpp' bytes zero + int pos = 0; + int tmp = 0; + + switch (bitDepth) { + case 1: + case 2: + case 4: + // Image can only have a single band + + int mask = samplesPerByte - 1; + for (int s = xOffset; s < numSamples; s += xSkip) { + byte val = scale0[samples[s]]; + tmp = (tmp << bitDepth) | val; + + if ((pos++ & mask) == mask) { + currRow[count++] = (byte) tmp; + tmp = 0; + pos = 0; + } + } + + // Left shift the last byte + if ((pos & mask) != 0) { + tmp <<= ((8 / bitDepth) - pos) * bitDepth; + currRow[count] = (byte) tmp; + } + break; + + case 8: + if (numBands == 1) { + for (int s = xOffset; s < numSamples; s += xSkip) { + currRow[count++] = scale0[samples[s]]; + if (icm_gray_alpha != null) { + currRow[count++] = + scale0[icm_gray_alpha.getAlpha(0xff & samples[s])]; + } + } + } else { + for (int s = xOffset; s < numSamples; s += xSkip) { + for (int b = 0; b < numBands; b++) { + currRow[count++] = scale[b][samples[s + b]]; + } + } + } + break; + + case 16: + for (int s = xOffset; s < numSamples; s += xSkip) { + for (int b = 0; b < numBands; b++) { + currRow[count++] = scaleh[b][samples[s + b]]; + currRow[count++] = scalel[b][samples[s + b]]; + } + } + break; + } + + // Perform filtering + int filterType = rowFilter.filterRow(metadata.IHDR_colorType, + currRow, prevRow, + filteredRows, + bytesPerRow, bpp); + + os.write(filterType); + os.write(filteredRows[filterType], bpp, bytesPerRow); + + // Swap current and previous rows + byte[] swap = currRow; + currRow = prevRow; + prevRow = swap; + + pixelsDone += hpixels; + processImageProgress(100.0F * pixelsDone / totalPixels); + + // If write has been aborted, just return; + // processWriteAborted will be called later + if (abortRequested()) { + return; + } + } + } + + // Use sourceXOffset, etc. + private void write_IDAT(RenderedImage image, int deflaterLevel) + throws IOException { + IDATOutputStreamBackport ios = new IDATOutputStreamBackport(stream, 32768, + deflaterLevel); + try { + if (metadata.IHDR_interlaceMethod == 1) { + for (int i = 0; i < 7; i++) { + encodePass(ios, image, + PNGImageReader.adam7XOffset[i], + PNGImageReader.adam7YOffset[i], + PNGImageReader.adam7XSubsampling[i], + PNGImageReader.adam7YSubsampling[i]); + if (abortRequested()) { + break; + } + } + } else { + encodePass(ios, image, 0, 0, 1, 1); + } + } finally { + ios.finish(); + } + } + + private void writeIEND() throws IOException { + ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.IEND_TYPE, stream); + cs.finish(); + } + + // Check two int arrays for value equality, always returns false + // if either array is null + private boolean equals(int[] s0, int[] s1) { + if (s0 == null || s1 == null) { + return false; + } + if (s0.length != s1.length) { + return false; + } + for (int i = 0; i < s0.length; i++) { + if (s0[i] != s1[i]) { + return false; + } + } + return true; + } + + // Initialize the scale/scale0 or scaleh/scalel arrays to + // hold the results of scaling an input value to the desired + // output bit depth + private void initializeScaleTables(int[] sampleSize) { + int bitDepth = metadata.IHDR_bitDepth; + + // If the existing tables are still valid, just return + if (bitDepth == scalingBitDepth && + equals(sampleSize, this.sampleSize)) { + return; + } + + // Compute new tables + this.sampleSize = sampleSize; + this.scalingBitDepth = bitDepth; + int maxOutSample = (1 << bitDepth) - 1; + if (bitDepth <= 8) { + scale = new byte[numBands][]; + for (int b = 0; b < numBands; b++) { + int maxInSample = (1 << sampleSize[b]) - 1; + int halfMaxInSample = maxInSample / 2; + scale[b] = new byte[maxInSample + 1]; + for (int s = 0; s <= maxInSample; s++) { + scale[b][s] = + (byte) ((s * maxOutSample + halfMaxInSample) / maxInSample); + } + } + scale0 = scale[0]; + scaleh = scalel = null; + } else { // bitDepth == 16 + // Divide scaling table into high and low bytes + scaleh = new byte[numBands][]; + scalel = new byte[numBands][]; + + for (int b = 0; b < numBands; b++) { + int maxInSample = (1 << sampleSize[b]) - 1; + int halfMaxInSample = maxInSample / 2; + scaleh[b] = new byte[maxInSample + 1]; + scalel[b] = new byte[maxInSample + 1]; + for (int s = 0; s <= maxInSample; s++) { + int val = (s * maxOutSample + halfMaxInSample) / maxInSample; + scaleh[b][s] = (byte) (val >> 8); + scalel[b][s] = (byte) (val & 0xff); + } + } + scale = null; + scale0 = null; + } + } + + @Override + public void write(IIOMetadata streamMetadata, + IIOImage image, + ImageWriteParam param) throws IIOException { + if (stream == null) { + throw new IllegalStateException("output == null!"); + } + if (image == null) { + throw new IllegalArgumentException("image == null!"); + } + if (image.hasRaster()) { + throw new UnsupportedOperationException("image has a Raster!"); + } + + RenderedImage im = image.getRenderedImage(); + SampleModel sampleModel = im.getSampleModel(); + this.numBands = sampleModel.getNumBands(); + + // Set source region and subsampling to default values + this.sourceXOffset = im.getMinX(); + this.sourceYOffset = im.getMinY(); + this.sourceWidth = im.getWidth(); + this.sourceHeight = im.getHeight(); + this.sourceBands = null; + this.periodX = 1; + this.periodY = 1; + + if (param != null) { + // Get source region and subsampling factors + Rectangle sourceRegion = param.getSourceRegion(); + if (sourceRegion != null) { + Rectangle imageBounds = new Rectangle(im.getMinX(), + im.getMinY(), + im.getWidth(), + im.getHeight()); + // Clip to actual image bounds + sourceRegion = sourceRegion.intersection(imageBounds); + sourceXOffset = sourceRegion.x; + sourceYOffset = sourceRegion.y; + sourceWidth = sourceRegion.width; + sourceHeight = sourceRegion.height; + } + + // Adjust for subsampling offsets + int gridX = param.getSubsamplingXOffset(); + int gridY = param.getSubsamplingYOffset(); + sourceXOffset += gridX; + sourceYOffset += gridY; + sourceWidth -= gridX; + sourceHeight -= gridY; + + // Get subsampling factors + periodX = param.getSourceXSubsampling(); + periodY = param.getSourceYSubsampling(); + + int[] sBands = param.getSourceBands(); + if (sBands != null) { + sourceBands = sBands; + numBands = sourceBands.length; + } + } + + // Compute output dimensions + int destWidth = (sourceWidth + periodX - 1) / periodX; + int destHeight = (sourceHeight + periodY - 1) / periodY; + if (destWidth <= 0 || destHeight <= 0) { + throw new IllegalArgumentException("Empty source region!"); + } + + // Compute total number of pixels for progress notification + this.totalPixels = destWidth * destHeight; + this.pixelsDone = 0; + + // Create metadata + IIOMetadata imd = image.getMetadata(); + if (imd != null) { + metadata = (PNGMetadata) convertImageMetadata(imd, + ImageTypeSpecifier.createFromRenderedImage(im), + null); + } else { + metadata = new PNGMetadata(); + } + + // reset compression level to default: + int deflaterLevel = DEFAULT_COMPRESSION_LEVEL; + + if (param != null) { + switch (param.getCompressionMode()) { + case ImageWriteParam.MODE_DISABLED: + deflaterLevel = Deflater.NO_COMPRESSION; + break; + case ImageWriteParam.MODE_EXPLICIT: + float quality = param.getCompressionQuality(); + if (quality >= 0f && quality <= 1f) { + deflaterLevel = 9 - Math.round(9f * quality); + } + break; + default: + } + + // Use Adam7 interlacing if set in write param + switch (param.getProgressiveMode()) { + case ImageWriteParam.MODE_DEFAULT: + metadata.IHDR_interlaceMethod = 1; + break; + case ImageWriteParam.MODE_DISABLED: + metadata.IHDR_interlaceMethod = 0; + break; + // MODE_COPY_FROM_METADATA should already be taken care of + // MODE_EXPLICIT is not allowed + default: + } + } + + // Initialize bitDepth and colorType + metadata.initialize(new ImageTypeSpecifier(im), numBands); + + // Overwrite IHDR width and height values with values from image + metadata.IHDR_width = destWidth; + metadata.IHDR_height = destHeight; + + this.bpp = numBands * ((metadata.IHDR_bitDepth == 16) ? 2 : 1); + + // Initialize scaling tables for this image + initializeScaleTables(sampleModel.getSampleSize()); + + clearAbortRequest(); + + processImageStarted(0); + + try { + write_magic(); + write_IHDR(); + + write_cHRM(); + write_gAMA(); + write_iCCP(); + write_sBIT(); + write_sRGB(); + + write_PLTE(); + + write_hIST(); + write_tRNS(); + write_bKGD(); + + write_pHYs(); + write_sPLT(); + write_tIME(); + write_tEXt(); + write_iTXt(); + write_zTXt(); + + writeUnknownChunks(); + + write_IDAT(im, deflaterLevel); + + if (abortRequested()) { + processWriteAborted(); + } else { + // Finish up and inform the listeners we are done + writeIEND(); + processImageComplete(); + } + } catch (IOException e) { + throw new IIOException("I/O error writing PNG file!", e); + } + } +} diff --git a/src/main/java/com/sun/imageio/plugins/png/PNGImageWriterSpiBackport.java b/src/main/java/com/sun/imageio/plugins/png/PNGImageWriterSpiBackport.java new file mode 100644 index 0000000..fc32bb8 --- /dev/null +++ b/src/main/java/com/sun/imageio/plugins/png/PNGImageWriterSpiBackport.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.png; + +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageWriter; +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.spi.ServiceRegistry; +import javax.imageio.stream.ImageOutputStream; +import java.awt.image.ColorModel; +import java.awt.image.IndexColorModel; +import java.awt.image.SampleModel; +import java.util.Iterator; +import java.util.Locale; + +public class PNGImageWriterSpiBackport extends ImageWriterSpi { + + private static final String vendorName = "xbib"; + + private static final String version = "1.0"; + + private static final String[] names = {"png", "PNG"}; + + private static final String[] suffixes = {"png"}; + + private static final String[] MIMETypes = {"image/png", "image/x-png"}; + + private static final String writerClassName = + "com.sun.imageio.plugins.png.PNGImageWriterBackport"; + + private static final String[] readerSpiNames = { + "com.sun.imageio.plugins.png.PNGImageReaderSpi" + }; + + public PNGImageWriterSpiBackport() { + super(vendorName, + version, + names, + suffixes, + MIMETypes, + writerClassName, + new Class[]{ImageOutputStream.class}, + readerSpiNames, + false, + null, null, + null, null, + true, + PNGMetadata.nativeMetadataFormatName, + "com.sun.imageio.plugins.png.PNGMetadataFormat", + null, null + ); + } + + @Override + public boolean canEncodeImage(ImageTypeSpecifier type) { + SampleModel sampleModel = type.getSampleModel(); + ColorModel colorModel = type.getColorModel(); + + // Find the maximum bit depth across all channels + int[] sampleSize = sampleModel.getSampleSize(); + int bitDepth = sampleSize[0]; + for (int i = 1; i < sampleSize.length; i++) { + if (sampleSize[i] > bitDepth) { + bitDepth = sampleSize[i]; + } + } + + // Ensure bitDepth is between 1 and 16 + if (bitDepth < 1 || bitDepth > 16) { + return false; + } + + // Check number of bands, alpha + int numBands = sampleModel.getNumBands(); + if (numBands < 1 || numBands > 4) { + return false; + } + + boolean hasAlpha = colorModel.hasAlpha(); + // Fix 4464413: PNGTransparency reg-test was failing + // because for IndexColorModels that have alpha, + // numBands == 1 && hasAlpha == true, thus causing + // the check below to fail and return false. + if (colorModel instanceof IndexColorModel) { + return true; + } + if ((numBands == 1 || numBands == 3) && hasAlpha) { + return false; + } + if ((numBands == 2 || numBands == 4) && !hasAlpha) { + return false; + } + + return true; + } + + @Override + public String getDescription(Locale locale) { + return "JDK9 Backport PNG image writer"; + } + + @Override + public ImageWriter createWriterInstance(Object extension) { + return new PNGImageWriterBackport(this); + } + + @Override + public void onRegistration(ServiceRegistry registry, Class category) { + Iterator others = registry.getServiceProviders(ImageWriterSpi.class, false); + while (others.hasNext()) { + ImageWriterSpi other = others.next(); + if (other != this) { + for (String formatName : other.getFormatNames()) { + if ("png".equals(formatName)) { + registry.setOrdering(ImageWriterSpi.class, this, other); + break; + } + } + } + } + } +} diff --git a/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi b/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi new file mode 100644 index 0000000..50a4998 --- /dev/null +++ b/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi @@ -0,0 +1 @@ +com.sun.imageio.plugins.png.PNGImageWriterSpiBackport \ No newline at end of file diff --git a/src/test/java/com/sun/imageio/plugins/png/PNGImageWriterBackportTest.java b/src/test/java/com/sun/imageio/plugins/png/PNGImageWriterBackportTest.java new file mode 100644 index 0000000..4427146 --- /dev/null +++ b/src/test/java/com/sun/imageio/plugins/png/PNGImageWriterBackportTest.java @@ -0,0 +1,85 @@ +package com.sun.imageio.plugins.png; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertTrue; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Iterator; + +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.stream.MemoryCacheImageOutputStream; + +import org.junit.Test; + +public class PNGImageWriterBackportTest { + + @Test + public void testPrecedence() { + Iterator it = ImageIO.getImageWritersByFormatName("png"); + assertTrue(it.next() instanceof PNGImageWriterBackport); + assertTrue(it.next() instanceof PNGImageWriter); + } + + @Test + public void testCompressionLevels() throws IOException { + + BufferedImage img = ImageIO.read(getClass().getResourceAsStream("placeholder-text.gif")); + + byte[] bd = toPng(img, null); // default + byte[] b00 = toPng(img, 0.0f); // highest compression, slowest + byte[] b01 = toPng(img, 0.1f); + byte[] b02 = toPng(img, 0.2f); + byte[] b03 = toPng(img, 0.3f); + byte[] b04 = toPng(img, 0.4f); + byte[] b05 = toPng(img, 0.5f); + byte[] b06 = toPng(img, 0.6f); + byte[] b07 = toPng(img, 0.7f); + byte[] b08 = toPng(img, 0.8f); + byte[] b09 = toPng(img, 0.9f); + byte[] b10 = toPng(img, 1.0f); // lowest compression, fastest + + assertArrayEquals(bd, b05); + assertArrayEquals(bd, b06); + + assertTrue(b00.length < b01.length); + assertTrue(b01.length < b02.length); + assertTrue(b02.length < b03.length); + assertTrue(b03.length < b04.length); + assertTrue(b04.length < b05.length); + assertTrue(b05.length == b06.length); + assertTrue(b06.length < b07.length); + assertTrue(b07.length < b08.length); + assertTrue(b08.length < b09.length); + assertTrue(b09.length < b10.length); + } + + private byte[] toPng(BufferedImage img, Float compressionQuality) throws IOException { + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Iterator imageWriters = ImageIO.getImageWritersByFormatName("png"); + while (imageWriters.hasNext()) { + ImageWriter writer = imageWriters.next(); + if (writer instanceof PNGImageWriterBackport) { + ImageWriteParam writeParam = writer.getDefaultWriteParam(); + if (compressionQuality != null) { + writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + writeParam.setCompressionQuality(compressionQuality); + } + try (ImageOutputStream stream = new MemoryCacheImageOutputStream(baos)) { + writer.setOutput(stream); + writer.write(null, new IIOImage(img, null, null), writeParam); + } finally { + writer.dispose(); + } + } + } + + return baos.toByteArray(); + } +} diff --git a/src/test/resources/com/sun/imageio/plugins/png/placeholder-text.gif b/src/test/resources/com/sun/imageio/plugins/png/placeholder-text.gif new file mode 100644 index 0000000..d905891 Binary files /dev/null and b/src/test/resources/com/sun/imageio/plugins/png/placeholder-text.gif differ