├── .gitignore ├── LICENSE ├── PLCT.svg ├── README.md ├── build.gradle.kts ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── image └── example-1.png ├── settings.gradle.kts ├── simple-png-imageio ├── build.gradle.kts └── src │ └── main │ └── java │ ├── module-info.java │ └── org │ └── glavo │ └── png │ └── imageio │ └── PNGImageIOUtils.java ├── simple-png-javafx ├── build.gradle.kts └── src │ └── main │ └── java │ ├── module-info.java │ └── org │ └── glavo │ └── png │ └── javafx │ └── PNGJavaFXUtils.java └── src ├── main └── java │ ├── module-info.java │ └── org │ └── glavo │ └── png │ ├── PNGFilterType.java │ ├── PNGMetadata.java │ ├── PNGType.java │ ├── PNGWriter.java │ └── image │ ├── ArgbImage.java │ ├── ArgbImageBuffer.java │ ├── ArgbImageWithMetadata.java │ └── ArgbImageWrapper.java └── test ├── java └── org │ └── glavo │ └── png │ ├── BasicTest.java │ ├── JavaFXTest.java │ ├── PNGWriterTest.java │ └── example │ ├── Gradient.java │ └── Transparent.java └── resources └── org └── glavo └── png ├── alex.png ├── background.jpg ├── minecraft.png ├── rgba.png ├── skin.png └── steve.png /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .idea/ 3 | .gradle/ 4 | 5 | /*.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 Glavo 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /PLCT.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SimplePNG 2 | 3 | A zero-dependency minimalist Java library for creating PNG files. 4 | 5 | It is implemented in pure Java and independent of Java AWT (ImageIO). 6 | It can be easily used with Android or JavaFX. 7 | 8 | ## Feature 9 | 10 | * Support for Java 8+; 11 | * No dependence, only needs the `java.base` module; 12 | * Very small (< 20 KiB); 13 | * Supports writing compressed or uncompressed PNG images; 14 | * Support for optional alpha channels; 15 | * Support writing PNG metadata. 16 | 17 | ## Limitations 18 | 19 | * Currently only true color images are supported, support for grayscale images is planned; 20 | * Currently only 8bpc images are supported; 21 | * PNG filter is not supported, it may be supported in the future; 22 | * Color palette is not supported. 23 | 24 | ## Adding SimplePNG to your build 25 | 26 | Maven: 27 | ```xml 28 | 29 | org.glavo 30 | simple-png 31 | 0.3.0 32 | 33 | ``` 34 | 35 | Gradle: 36 | ```kotlin 37 | implementation("org.glavo:simple-png:0.3.0") 38 | ``` 39 | 40 | If you want to use it for JavaFX, you can include additional dependencies: 41 | 42 | ```xml 43 | 44 | org.glavo 45 | simple-png-javafx 46 | 0.3.0 47 | 48 | ``` 49 | 50 | Gradle: 51 | ```kotlin 52 | implementation("org.glavo:simple-png-javafx:0.3.0") 53 | ``` 54 | 55 | ## Usage 56 | 57 | The core abstraction of SimplePNG represents the [`ArgbImage`](src/main/java/org/glavo/png/image/ArgbImage.java) interface 58 | of true color images and the [`PNGWriter`](src/main/java/org/glavo/png/PNGWriter.java) used to write images to the output stream. 59 | 60 | This is a simple example: 61 | 62 | ```java 63 | int rgba(int r,int g,int b,int a) { return ((r << 16) | ((g) << 8) | ((b)) | ((a) << 24)); } 64 | 65 | ArgbImage image = new ArgbImage() { 66 | public int getWidth() { return 250; } 67 | public int getHeight() { return 250; } 68 | public int getArgb(int x, int y) { return rgba(x & 255, y & 255, 128, (255 - ((x / 2) & 255))); } 69 | } 70 | 71 | try (PNGWriter writer = new PNGWriter(Files.newOutputStream(Paths.get("gradient.png")))) { 72 | writer.write(image); 73 | } 74 | ``` 75 | 76 | Result: 77 | 78 | ![gradient.png](image/example-1.png) 79 | 80 | In addition to directly implementing the `ArgbImage` interface, 81 | SimplePNG also has some `ArgbImage` built-in implementations to facilitate you to interact with Java AWT/JavaFX or draw pictures. 82 | 83 | ```java 84 | // Array-based writable image 85 | ArgbImageBuffer buffer = new ArgbImageBuffer(); 86 | for (int x = 0; x < 250; x++) { 87 | for (int y = 0; y < 250; y++) { 88 | buffer.setArgb(x, y, (255 - ((x / 2) & 255)), x & 255, y & 255, 128); 89 | } 90 | } 91 | 92 | // Wrapper for javafx.scene.image.Image 93 | ArgbImage fxImage = PNGJavaFXUtils.asArgbImage(new Image("image.jpg")); 94 | ``` 95 | 96 | ### `PNGWriter` 97 | 98 | PNGWriter provides a set of configurable options. 99 | 100 | By default, `PNGWriter` will write files using 32-bit color (8 bits per channel, with an alpha channel). 101 | You can remove the alpha channel by setting the type to RGB. 102 | 103 | ```java 104 | new PNGWriter(outputStream, PNGType.RGB) 105 | ``` 106 | 107 | By default, `PNGWriter` will compress images using the default compression level. 108 | You can modify the compression level of `PNGWriter`. 109 | 110 | ```java 111 | new PNGWriter(outputStream, 0) 112 | ``` 113 | 114 | The lowest compression level is `0`, which means no compression; 115 | compression levels up to `9`, take more time to compress but have the smallest end result. 116 | 117 | ### Metadata 118 | 119 | SimplePNG supports attaching metadata to PNG images. 120 | 121 | ```java 122 | ArgbImage image = ...; 123 | 124 | PNGMetadata metadta = new PNGMetadata() 125 | .setAuthor("Glavo") 126 | .setComment("SimplePNG example image"); 127 | 128 | ArgbImage imageWithMetadata = image.withMetadata(metadata); 129 | 130 | try (PNGWriter writer = ...) { 131 | writer.write(imageWithMetadata); 132 | } 133 | ``` 134 | 135 | SimplePNG will write the metadata into PNG files. 136 | 137 | ## Donate 138 | 139 | If you like this library, donating to me is my greatest support! 140 | 141 | Due to payment method restrictions, donations are currently only supported through payment channels in Chinese mainland (微信,支付宝,爱发电等). 142 | 143 | Here are the ways to donate: [捐赠支持 Glavo](https://donate.glavo.site/) 144 | 145 | ## Especially thanks 146 | 147 | PLCT Logo 148 | 149 | Thanks to [PLCT Lab](https://plctlab.org) for supporting me. 150 | 151 | ![IntelliJ IDEA logo](https://resources.jetbrains.com/storage/products/company/brand/logos/IntelliJ_IDEA.svg) 152 | 153 | This project is developed using JetBrains IDEA. 154 | Thanks to JetBrains for providing me with a free license, which is a strong support for me. 155 | 156 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { mavenCentral() } 3 | 4 | dependencies { classpath("org.glavo.kala:kala-platform:0.10.0") } 5 | } 6 | 7 | plugins { 8 | id("java-library") 9 | id("maven-publish") 10 | id("signing") 11 | id("io.github.gradle-nexus.publish-plugin") version "1.1.0" 12 | id("org.glavo.load-maven-publish-properties") version "0.1.0" 13 | id("org.glavo.compile-module-info-plugin") version "2.0" 14 | } 15 | 16 | allprojects { 17 | 18 | apply { 19 | plugin("java-library") 20 | plugin("maven-publish") 21 | plugin("signing") 22 | plugin("org.glavo.compile-module-info-plugin") 23 | } 24 | 25 | group = "org.glavo" 26 | version = "0.4.0" + "-SNAPSHOT" 27 | description = "Minimal library for creating PNG images" 28 | 29 | java { 30 | withSourcesJar() 31 | } 32 | 33 | val javadocJar = tasks.create("javadocJar") { 34 | group = "build" 35 | archiveClassifier.set("javadoc") 36 | } 37 | 38 | tasks.compileJava { 39 | options.release.set(8) 40 | 41 | sourceCompatibility = "9" 42 | } 43 | 44 | tasks.compileTestJava { 45 | options.release.set(17) 46 | } 47 | 48 | repositories { 49 | mavenCentral() 50 | } 51 | 52 | dependencies { 53 | if (project != rootProject) { 54 | api(rootProject) 55 | } 56 | } 57 | 58 | tasks.getByName("test") { 59 | useJUnitPlatform() 60 | } 61 | 62 | tasks.withType { 63 | enabled = false 64 | } 65 | 66 | configure { 67 | publications { 68 | create("maven") { 69 | groupId = project.group.toString() 70 | version = project.version.toString() 71 | artifactId = project.name 72 | 73 | from(components["java"]) 74 | artifact(javadocJar) 75 | 76 | pom { 77 | name.set(project.name) 78 | description.set(project.description) 79 | url.set("https://github.com/Glavo/SimplePNG") 80 | 81 | licenses { 82 | license { 83 | name.set("Apache-2.0") 84 | url.set("https://www.apache.org/licenses/LICENSE-2.0.txt") 85 | } 86 | } 87 | 88 | developers { 89 | developer { 90 | id.set("glavo") 91 | name.set("Glavo") 92 | email.set("zjx001202@gmail.com") 93 | } 94 | } 95 | 96 | scm { 97 | url.set("https://github.com/Glavo/SimplePNG") 98 | } 99 | } 100 | } 101 | } 102 | } 103 | 104 | if (rootProject.ext.has("signing.key")) { 105 | signing { 106 | useInMemoryPgpKeys( 107 | rootProject.ext["signing.keyId"].toString(), 108 | rootProject.ext["signing.key"].toString(), 109 | rootProject.ext["signing.password"].toString(), 110 | ) 111 | sign(publishing.publications["maven"]) 112 | } 113 | } 114 | 115 | } 116 | 117 | dependencies { 118 | testImplementation(project(":simple-png-imageio")) 119 | testImplementation(project(":simple-png-javafx")) 120 | 121 | testImplementation("org.apache.commons:commons-imaging:1.0-alpha3") 122 | testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.0") 123 | testImplementation("org.junit.jupiter:junit-jupiter-params:5.9.0") 124 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.9.0") 125 | } 126 | 127 | // Setup JavaFX 128 | run { 129 | var classifer = when (kala.platform.Platform.CURRENT_PLATFORM.operatingSystem) { 130 | kala.platform.OperatingSystem.LINUX -> "linux" 131 | kala.platform.OperatingSystem.WINDOWS -> "win" 132 | kala.platform.OperatingSystem.MACOS -> "mac" 133 | else -> return@run 134 | } 135 | 136 | when (kala.platform.Platform.CURRENT_PLATFORM.architecture) { 137 | kala.platform.Architecture.X86_64 -> {} 138 | kala.platform.Architecture.X86 -> classifer += "-x86" 139 | kala.platform.Architecture.AARCH64 -> classifer += "-aarch64" 140 | kala.platform.Architecture.ARM -> if (classifer == "linux") classifer = "linux-arm32-monocle" else return@run 141 | else -> return@run 142 | } 143 | 144 | val modules = listOf("base", "graphics") 145 | 146 | dependencies { 147 | for (module in modules) { 148 | testImplementation("org.openjfx:javafx-$module:17.0.2:$classifer") 149 | } 150 | } 151 | } 152 | 153 | // ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository 154 | nexusPublishing { 155 | repositories { 156 | sonatype { 157 | stagingProfileId.set(rootProject.ext["sonatypeStagingProfileId"].toString()) 158 | username.set(rootProject.ext["sonatypeUsername"].toString()) 159 | password.set(rootProject.ext["sonatypePassword"].toString()) 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Glavo/SimplePNG/c0c9c5ee17d5d3ba6a4f8c3e02de883029bc4134/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s 90 | ' "$PWD" ) || exit 91 | 92 | # Use the maximum available, or set MAX_FD != -1 to use that value. 93 | MAX_FD=maximum 94 | 95 | warn () { 96 | echo "$*" 97 | } >&2 98 | 99 | die () { 100 | echo 101 | echo "$*" 102 | echo 103 | exit 1 104 | } >&2 105 | 106 | # OS specific support (must be 'true' or 'false'). 107 | cygwin=false 108 | msys=false 109 | darwin=false 110 | nonstop=false 111 | case "$( uname )" in #( 112 | CYGWIN* ) cygwin=true ;; #( 113 | Darwin* ) darwin=true ;; #( 114 | MSYS* | MINGW* ) msys=true ;; #( 115 | NONSTOP* ) nonstop=true ;; 116 | esac 117 | 118 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 119 | 120 | 121 | # Determine the Java command to use to start the JVM. 122 | if [ -n "$JAVA_HOME" ] ; then 123 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 124 | # IBM's JDK on AIX uses strange locations for the executables 125 | JAVACMD=$JAVA_HOME/jre/sh/java 126 | else 127 | JAVACMD=$JAVA_HOME/bin/java 128 | fi 129 | if [ ! -x "$JAVACMD" ] ; then 130 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 131 | 132 | Please set the JAVA_HOME variable in your environment to match the 133 | location of your Java installation." 134 | fi 135 | else 136 | JAVACMD=java 137 | if ! command -v java >/dev/null 2>&1 138 | then 139 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 140 | 141 | Please set the JAVA_HOME variable in your environment to match the 142 | location of your Java installation." 143 | fi 144 | fi 145 | 146 | # Increase the maximum file descriptors if we can. 147 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 148 | case $MAX_FD in #( 149 | max*) 150 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 151 | # shellcheck disable=SC2039,SC3045 152 | MAX_FD=$( ulimit -H -n ) || 153 | warn "Could not query maximum file descriptor limit" 154 | esac 155 | case $MAX_FD in #( 156 | '' | soft) :;; #( 157 | *) 158 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 159 | # shellcheck disable=SC2039,SC3045 160 | ulimit -n "$MAX_FD" || 161 | warn "Could not set maximum file descriptor limit to $MAX_FD" 162 | esac 163 | fi 164 | 165 | # Collect all arguments for the java command, stacking in reverse order: 166 | # * args from the command line 167 | # * the main class name 168 | # * -classpath 169 | # * -D...appname settings 170 | # * --module-path (only if needed) 171 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 172 | 173 | # For Cygwin or MSYS, switch paths to Windows format before running java 174 | if "$cygwin" || "$msys" ; then 175 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 176 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 177 | 178 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 179 | 180 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 181 | for arg do 182 | if 183 | case $arg in #( 184 | -*) false ;; # don't mess with options #( 185 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 186 | [ -e "$t" ] ;; #( 187 | *) false ;; 188 | esac 189 | then 190 | arg=$( cygpath --path --ignore --mixed "$arg" ) 191 | fi 192 | # Roll the args list around exactly as many times as the number of 193 | # args, so each arg winds up back in the position where it started, but 194 | # possibly modified. 195 | # 196 | # NB: a `for` loop captures its iteration list before it begins, so 197 | # changing the positional parameters here affects neither the number of 198 | # iterations, nor the values presented in `arg`. 199 | shift # remove old arg 200 | set -- "$@" "$arg" # push replacement arg 201 | done 202 | fi 203 | 204 | 205 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 206 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 207 | 208 | # Collect all arguments for the java command: 209 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 210 | # and any embedded shellness will be escaped. 211 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 212 | # treated as '${Hostname}' itself on the command line. 213 | 214 | set -- \ 215 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 216 | -classpath "$CLASSPATH" \ 217 | org.gradle.wrapper.GradleWrapperMain \ 218 | "$@" 219 | 220 | # Stop when "xargs" is not available. 221 | if ! command -v xargs >/dev/null 2>&1 222 | then 223 | die "xargs is not available" 224 | fi 225 | 226 | # Use "xargs" to parse quoted args. 227 | # 228 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 229 | # 230 | # In Bash we could simply go: 231 | # 232 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 233 | # set -- "${ARGS[@]}" "$@" 234 | # 235 | # but POSIX shell has neither arrays nor command substitution, so instead we 236 | # post-process each arg (as a line of input to sed) to backslash-escape any 237 | # character that might be a shell metacharacter, then use eval to reverse 238 | # that process (while maintaining the separation between arguments), and wrap 239 | # the whole thing up as a single "set" statement. 240 | # 241 | # This will of course break if any of these variables contains a newline or 242 | # an unmatched quote. 243 | # 244 | 245 | eval "set -- $( 246 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 247 | xargs -n1 | 248 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 249 | tr '\n' ' ' 250 | )" '"$@"' 251 | 252 | exec "$JAVACMD" "$@" 253 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /image/example-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Glavo/SimplePNG/c0c9c5ee17d5d3ba6a4f8c3e02de883029bc4134/image/example-1.png -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "simple-png" 2 | 3 | include("simple-png-javafx", "simple-png-imageio") -------------------------------------------------------------------------------- /simple-png-imageio/build.gradle.kts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Glavo/SimplePNG/c0c9c5ee17d5d3ba6a4f8c3e02de883029bc4134/simple-png-imageio/build.gradle.kts -------------------------------------------------------------------------------- /simple-png-imageio/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Glavo 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | module org.glavo.png.imageio { 18 | requires org.glavo.png; 19 | requires java.desktop; 20 | 21 | exports org.glavo.png.imageio; 22 | } -------------------------------------------------------------------------------- /simple-png-imageio/src/main/java/org/glavo/png/imageio/PNGImageIOUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Glavo 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.glavo.png.imageio; 17 | 18 | import org.glavo.png.image.ArgbImageWrapper; 19 | 20 | import java.awt.image.BufferedImage; 21 | import java.util.Objects; 22 | 23 | public final class PNGImageIOUtils { 24 | private PNGImageIOUtils() { 25 | } 26 | 27 | public static ArgbImageWrapper asArgbImage(BufferedImage image) { 28 | Objects.requireNonNull(image); 29 | return new ArgbImageWrapper(image, image.getWidth(), image.getHeight()) { 30 | @Override 31 | public int getArgb(int x, int y) { 32 | return image.getRGB(x, y); 33 | } 34 | }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /simple-png-javafx/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compileOnly("org.openjfx:javafx-graphics:17.0.2:linux") 3 | } -------------------------------------------------------------------------------- /simple-png-javafx/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Glavo 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | module org.glavo.png.javafx { 18 | requires org.glavo.png; 19 | requires javafx.graphics; 20 | 21 | exports org.glavo.png.javafx; 22 | } -------------------------------------------------------------------------------- /simple-png-javafx/src/main/java/org/glavo/png/javafx/PNGJavaFXUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Glavo 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.glavo.png.javafx; 17 | 18 | import javafx.scene.image.Image; 19 | import javafx.scene.image.PixelReader; 20 | import org.glavo.png.PNGType; 21 | import org.glavo.png.PNGWriter; 22 | import org.glavo.png.image.ArgbImageWrapper; 23 | 24 | import java.io.ByteArrayOutputStream; 25 | import java.io.IOException; 26 | import java.io.OutputStream; 27 | import java.io.UncheckedIOException; 28 | import java.nio.file.Files; 29 | import java.nio.file.Path; 30 | 31 | public final class PNGJavaFXUtils { 32 | private PNGJavaFXUtils() { 33 | } 34 | 35 | public static ArgbImageWrapper asArgbImage(Image image) { 36 | PixelReader pixelReader = image.getPixelReader(); 37 | 38 | return new ArgbImageWrapper(image, (int) image.getWidth(), (int) image.getHeight()) { 39 | @Override 40 | public int getArgb(int x, int y) { 41 | return pixelReader.getArgb(x, y); 42 | } 43 | }; 44 | } 45 | 46 | public static void writeImage(Image image, Path file) throws IOException { 47 | writeImage(image, Files.newOutputStream(file)); 48 | } 49 | 50 | public static void writeImage(Image image, Path file, PNGType type) throws IOException { 51 | writeImage(image, Files.newOutputStream(file), type); 52 | } 53 | 54 | public static void writeImage(Image image, Path file, PNGType type, int compressLevel) throws IOException { 55 | writeImage(image, Files.newOutputStream(file), type, compressLevel); 56 | } 57 | 58 | public static byte[] writeImageToArray(Image image) { 59 | return writeImageToArray(image, PNGType.RGBA, PNGWriter.DEFAULT_COMPRESS_LEVEL); 60 | } 61 | 62 | public static byte[] writeImageToArray(Image image, PNGType type) { 63 | return writeImageToArray(image, type, PNGWriter.DEFAULT_COMPRESS_LEVEL); 64 | } 65 | 66 | public static byte[] writeImageToArray(Image image, PNGType type, int compressLevel) { 67 | int estimatedSize = (int) ( 68 | image.getWidth() * image.getHeight() 69 | * (type == PNGType.RGB ? 3 : 4) 70 | + 32); 71 | 72 | if (compressLevel != 1) { 73 | estimatedSize /= 2; 74 | } 75 | 76 | try { 77 | ByteArrayOutputStream temp = new ByteArrayOutputStream(Integer.max(estimatedSize, 32)); 78 | writeImage(image, temp, type, compressLevel); 79 | return temp.toByteArray(); 80 | } catch (IOException e) { 81 | throw new UncheckedIOException(e); 82 | } 83 | } 84 | 85 | private static void writeImage(Image image, OutputStream out) throws IOException { 86 | writeImage(image, out, PNGType.RGBA, PNGWriter.DEFAULT_COMPRESS_LEVEL); 87 | } 88 | 89 | private static void writeImage(Image image, OutputStream out, PNGType type) throws IOException { 90 | writeImage(image, out, type, PNGWriter.DEFAULT_COMPRESS_LEVEL); 91 | } 92 | 93 | private static void writeImage(Image image, OutputStream out, PNGType type, int compressLevel) throws IOException { 94 | new PNGWriter(out, type, compressLevel).write(asArgbImage(image)); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Glavo 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | module org.glavo.png { 18 | requires static java.desktop; 19 | requires static javafx.graphics; 20 | 21 | exports org.glavo.png; 22 | exports org.glavo.png.image; 23 | } -------------------------------------------------------------------------------- /src/main/java/org/glavo/png/PNGFilterType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Glavo 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.glavo.png; 17 | 18 | public enum PNGFilterType { 19 | NONE(0), 20 | SUB(1), 21 | UP(2), 22 | AVERAGE(3), 23 | PAETH(4) 24 | ; 25 | 26 | final int id; 27 | 28 | PNGFilterType(int id) { 29 | this.id = id; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/glavo/png/PNGMetadata.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Glavo 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.glavo.png; 17 | 18 | import java.io.Serializable; 19 | import java.nio.file.attribute.FileTime; 20 | import java.time.*; 21 | import java.time.format.DateTimeFormatter; 22 | import java.util.Collections; 23 | import java.util.Date; 24 | import java.util.LinkedHashMap; 25 | import java.util.Map; 26 | 27 | public final class PNGMetadata implements Serializable { 28 | public final static String KEY_TITLE = "Title"; // Short (one line) title or caption for image 29 | public final static String KEY_AUTHOR = "Author"; // Name of image's creator 30 | public final static String KEY_DESCRIPTION = "Description"; // Description of image (possibly long) 31 | public final static String KEY_COPYRIGHT = "Copyright"; // Copyright notice 32 | public final static String KEY_CREATION_TIME = "Creation Time"; // Time of original image creation 33 | public final static String KEY_SOFTWARE = "Software"; // Software used to create the image 34 | public final static String KEY_DISCLAIMER = "Disclaimer"; // Legal disclaimer 35 | public final static String KEY_WARNING = "Warning"; // Warning of nature of content 36 | public final static String KEY_SOURCE = "Source"; // Device used to create the image 37 | public final static String KEY_COMMENT = "Comment"; // Miscellaneous comment 38 | 39 | private static final long serialVersionUID = 0L; 40 | 41 | Map texts = Collections.emptyMap(); 42 | 43 | public PNGMetadata() { 44 | } 45 | 46 | public PNGMetadata setText(String key, String value) { 47 | if (texts.isEmpty()) 48 | texts = new LinkedHashMap<>(); 49 | 50 | texts.put(key, value); 51 | return this; 52 | } 53 | 54 | public String getText(String key) { 55 | return texts.get(key); 56 | } 57 | 58 | public PNGMetadata setTitle(String title) { 59 | setText(KEY_TITLE, title); 60 | return this; 61 | } 62 | 63 | public PNGMetadata setAuthor(String author) { 64 | setText(KEY_AUTHOR, author); 65 | return this; 66 | } 67 | 68 | public PNGMetadata setAuthor() { 69 | setText(KEY_AUTHOR, System.getProperty("user.name")); 70 | return this; 71 | } 72 | 73 | public PNGMetadata setDescription(String description) { 74 | setText(KEY_DESCRIPTION, description); 75 | return this; 76 | } 77 | 78 | public PNGMetadata setCopyright(String copyright) { 79 | setText(KEY_COPYRIGHT, copyright); 80 | return this; 81 | } 82 | 83 | public PNGMetadata setCreationTime(String creationTime) { 84 | setText(KEY_CREATION_TIME, creationTime); 85 | return this; 86 | } 87 | 88 | public PNGMetadata setCreationTime(LocalDateTime time) { 89 | setCreationTime(ZonedDateTime.of(time, ZoneOffset.UTC).toOffsetDateTime()); 90 | return this; 91 | } 92 | 93 | public PNGMetadata setCreationTime(OffsetDateTime time) { 94 | setCreationTime(time.format(DateTimeFormatter.RFC_1123_DATE_TIME)); 95 | return this; 96 | } 97 | 98 | public PNGMetadata setCreationTime(FileTime time) { 99 | setCreationTime(ZonedDateTime.ofInstant(time.toInstant(), ZoneOffset.UTC).toOffsetDateTime()); 100 | return this; 101 | } 102 | 103 | public PNGMetadata setCreationTime() { 104 | setCreationTime(LocalDateTime.now()); 105 | return this; 106 | } 107 | 108 | public PNGMetadata setSoftware(String software) { 109 | setText(KEY_SOFTWARE, software); 110 | return this; 111 | } 112 | 113 | public PNGMetadata setDisclaimer(String disclaimer) { 114 | setText(KEY_DISCLAIMER, disclaimer); 115 | return this; 116 | } 117 | 118 | public PNGMetadata setWarning(String warning) { 119 | setText(KEY_WARNING, warning); 120 | return this; 121 | } 122 | 123 | public PNGMetadata setSource(String source) { 124 | setText(KEY_SOURCE, source); 125 | return this; 126 | } 127 | 128 | public PNGMetadata setComment(String comment) { 129 | setText(KEY_COMMENT, comment); 130 | return this; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/org/glavo/png/PNGType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Glavo 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.glavo.png; 17 | 18 | public enum PNGType { 19 | GRAYSCALE(0, 1), 20 | RGB(2, 3), 21 | PALETTE(3, 1), 22 | GRAYSCALE_ALPHA(4, 2), 23 | RGBA(6, 4); 24 | 25 | final int id; 26 | final int cpp; 27 | 28 | PNGType(int id, int cpp) { 29 | this.id = id; 30 | this.cpp = cpp; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/glavo/png/PNGWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Glavo 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.glavo.png; 17 | 18 | import org.glavo.png.image.ArgbImage; 19 | 20 | import java.io.ByteArrayOutputStream; 21 | import java.io.Closeable; 22 | import java.io.IOException; 23 | import java.io.OutputStream; 24 | import java.nio.charset.StandardCharsets; 25 | import java.util.Map; 26 | import java.util.Objects; 27 | import java.util.zip.CRC32; 28 | import java.util.zip.Deflater; 29 | import java.util.zip.DeflaterOutputStream; 30 | 31 | public final class PNGWriter implements Closeable { 32 | public static final int DEFAULT_COMPRESS_LEVEL = Deflater.DEFAULT_COMPRESSION; 33 | 34 | private static final int COMPRESS_THRESHOLD = 20; 35 | private static final byte[] PNG_FILE_HEADER = { 36 | (byte) 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A 37 | }; 38 | 39 | private final OutputStream out; 40 | private final PNGType type; 41 | private final int compressLevel; 42 | 43 | private final Deflater deflater = new Deflater(); 44 | private final CRC32 crc32 = new CRC32(); 45 | private final byte[] writeBuffer = new byte[8]; 46 | 47 | public PNGWriter(OutputStream out) { 48 | this(out, PNGType.RGBA, DEFAULT_COMPRESS_LEVEL); 49 | } 50 | 51 | public PNGWriter(OutputStream out, PNGType type) { 52 | this(out, type, DEFAULT_COMPRESS_LEVEL); 53 | } 54 | 55 | public PNGWriter(OutputStream out, int compressLevel) { 56 | this(out, PNGType.RGBA, compressLevel); 57 | } 58 | 59 | public PNGWriter(OutputStream out, PNGType type, int compressLevel) { 60 | Objects.requireNonNull(type); 61 | Objects.requireNonNull(out); 62 | 63 | if (compressLevel != Deflater.DEFAULT_COMPRESSION && (compressLevel < 0 || compressLevel > 9)) { 64 | throw new IllegalArgumentException(); 65 | } 66 | 67 | if (type != PNGType.RGB && type != PNGType.RGBA) { 68 | throw new UnsupportedOperationException("SimplePNG currently only supports RGB or RGBA images"); 69 | } 70 | 71 | this.type = type; 72 | this.compressLevel = compressLevel; 73 | this.out = out; 74 | 75 | this.deflater.setLevel(compressLevel); 76 | } 77 | 78 | public PNGType getType() { 79 | return type; 80 | } 81 | 82 | public int getCompressLevel() { 83 | return compressLevel; 84 | } 85 | 86 | private void writeByte(int b) throws IOException { 87 | out.write(b); 88 | crc32.update(b); 89 | } 90 | 91 | private void writeShort(int s) throws IOException { 92 | writeBuffer[0] = (byte) (s >>> 8); 93 | writeBuffer[1] = (byte) (s >>> 0); 94 | writeBytes(writeBuffer, 0, 2); 95 | } 96 | 97 | private void writeInt(int value) throws IOException { 98 | writeBuffer[0] = (byte) (value >>> 24); 99 | writeBuffer[1] = (byte) (value >>> 16); 100 | writeBuffer[2] = (byte) (value >>> 8); 101 | writeBuffer[3] = (byte) (value >>> 0); 102 | writeBytes(writeBuffer, 0, 4); 103 | } 104 | 105 | private void writeLong(long value) throws IOException { 106 | writeBuffer[0] = (byte) (value >>> 56); 107 | writeBuffer[1] = (byte) (value >>> 48); 108 | writeBuffer[2] = (byte) (value >>> 40); 109 | writeBuffer[3] = (byte) (value >>> 32); 110 | writeBuffer[4] = (byte) (value >>> 24); 111 | writeBuffer[5] = (byte) (value >>> 16); 112 | writeBuffer[6] = (byte) (value >>> 8); 113 | writeBuffer[7] = (byte) (value >>> 0); 114 | writeBytes(writeBuffer, 0, 8); 115 | } 116 | 117 | private void writeBytes(byte[] bytes) throws IOException { 118 | writeBytes(bytes, 0, bytes.length); 119 | } 120 | 121 | private void writeBytes(byte[] bytes, int off, int len) throws IOException { 122 | out.write(bytes, off, len); 123 | crc32.update(bytes, off, len); 124 | } 125 | 126 | private void beginChunk(String header, int length) throws IOException { 127 | writeBuffer[0] = (byte) (length >>> 24); 128 | writeBuffer[1] = (byte) (length >>> 16); 129 | writeBuffer[2] = (byte) (length >>> 8); 130 | writeBuffer[3] = (byte) (length >>> 0); 131 | writeBuffer[4] = (byte) header.charAt(0); 132 | writeBuffer[5] = (byte) header.charAt(1); 133 | writeBuffer[6] = (byte) header.charAt(2); 134 | writeBuffer[7] = (byte) header.charAt(3); 135 | out.write(writeBuffer, 0, 8); 136 | 137 | crc32.reset(); 138 | crc32.update(writeBuffer, 4, 4); 139 | } 140 | 141 | private void endChunk() throws IOException { 142 | int crc = (int) crc32.getValue(); 143 | writeBuffer[0] = (byte) (crc >>> 24); 144 | writeBuffer[1] = (byte) (crc >>> 16); 145 | writeBuffer[2] = (byte) (crc >>> 8); 146 | writeBuffer[3] = (byte) (crc >>> 0); 147 | out.write(writeBuffer, 0, 4); 148 | } 149 | 150 | private static final byte[] textSeparator = {0}; 151 | 152 | private void textChunk(String keyword, String text) throws IOException { 153 | byte[] keywordBytes = keyword.getBytes(StandardCharsets.US_ASCII); 154 | byte[] textBytes = text.getBytes(StandardCharsets.UTF_8); 155 | int textBytesLength = textBytes.length; 156 | 157 | boolean isAscii = text.length() == textBytesLength; 158 | 159 | boolean compress = compressLevel != 0 && textBytesLength >= COMPRESS_THRESHOLD; 160 | 161 | if (compress) { 162 | byte[] compressed = new byte[textBytesLength]; 163 | 164 | deflater.reset(); 165 | deflater.setInput(textBytes); 166 | deflater.finish(); 167 | 168 | int len = deflater.deflate(compressed, 0, textBytesLength, Deflater.SYNC_FLUSH); 169 | if (len < textBytesLength) { 170 | textBytes = compressed; 171 | textBytesLength = len; 172 | } else { 173 | compress = false; 174 | deflater.reset(); 175 | } 176 | } 177 | 178 | 179 | String chunkType; 180 | 181 | byte[] separator; 182 | 183 | if (isAscii) { 184 | if (compress) { 185 | chunkType = "zTXt"; 186 | separator = new byte[]{ 187 | 0, // null separator 188 | 0 // compression method 189 | }; 190 | } else { 191 | chunkType = "tEXt"; 192 | separator = new byte[]{ 193 | 0, // null separator 194 | }; 195 | } 196 | } else { 197 | chunkType = "iTXt"; 198 | separator = new byte[] { 199 | 0, // null separator 200 | (byte) (compress ? 1 : 0), // compression flag 201 | 0, // compression method 202 | 0, // null separator 203 | 0 // null separator 204 | }; 205 | } 206 | 207 | beginChunk(chunkType, keywordBytes.length + separator.length + textBytesLength); 208 | writeBytes(keywordBytes); 209 | writeBytes(separator); 210 | writeBytes(textBytes, 0, textBytesLength); 211 | endChunk(); 212 | } 213 | 214 | public void write(ArgbImage image) throws IOException { 215 | PNGMetadata metadata = image.getMetadata(); 216 | final int width = image.getWidth(); 217 | final int height = image.getHeight(); 218 | 219 | out.write(PNG_FILE_HEADER); 220 | 221 | // IHDR Chunk 222 | beginChunk("IHDR", 13); 223 | writeInt(width); 224 | writeInt(height); 225 | writeByte(8); // bit depth 226 | writeByte(type.id); 227 | writeByte(0); // compression 228 | writeByte(0); // filter 229 | writeByte(0); // interlace method 230 | endChunk(); 231 | 232 | if (metadata != null) { 233 | for (Map.Entry entry : metadata.texts.entrySet()) { 234 | textChunk(entry.getKey(), entry.getValue()); 235 | } 236 | } 237 | 238 | // IDAT Chunk 239 | int colorPerPixel = type.cpp; 240 | int bytesPerLine = 1 + colorPerPixel * width; 241 | int rawOutputSize = bytesPerLine * height; 242 | byte[] lineBuffer = new byte[bytesPerLine]; 243 | 244 | deflater.reset(); 245 | OutputBuffer buffer = new OutputBuffer(compressLevel == 0 ? rawOutputSize + 12 : rawOutputSize / 2); 246 | try (DeflaterOutputStream dos = new DeflaterOutputStream(buffer, deflater)) { 247 | for (int y = 0; y < height; y++) { 248 | for (int x = 0; x < width; x++) { 249 | int color = image.getArgb(x, y); 250 | int off = 1 + colorPerPixel * x; 251 | 252 | lineBuffer[off + 0] = (byte) (color >>> 16); 253 | lineBuffer[off + 1] = (byte) (color >>> 8); 254 | lineBuffer[off + 2] = (byte) (color >>> 0); 255 | if (colorPerPixel == 4) 256 | lineBuffer[off + 3] = (byte) (color >>> 24); 257 | } 258 | 259 | dos.write(lineBuffer); 260 | } 261 | } 262 | 263 | int len = buffer.size(); 264 | beginChunk("IDAT", len); 265 | writeBytes(buffer.getBuffer(), 0, len); 266 | endChunk(); 267 | 268 | // IEND Chunk 269 | beginChunk("IEND", 0); 270 | endChunk(); 271 | } 272 | 273 | @Override 274 | public void close() throws IOException { 275 | deflater.end(); 276 | out.close(); 277 | } 278 | 279 | private static final class OutputBuffer extends ByteArrayOutputStream { 280 | public OutputBuffer() { 281 | } 282 | 283 | public OutputBuffer(int size) { 284 | super(size); 285 | } 286 | 287 | byte[] getBuffer() { 288 | return super.buf; 289 | } 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /src/main/java/org/glavo/png/image/ArgbImage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Glavo 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.glavo.png.image; 18 | 19 | import org.glavo.png.PNGMetadata; 20 | 21 | public interface ArgbImage { 22 | 23 | int getWidth(); 24 | 25 | int getHeight(); 26 | 27 | int getArgb(int x, int y); 28 | 29 | default PNGMetadata getMetadata() { 30 | return null; 31 | } 32 | 33 | default ArgbImage withMetadata(PNGMetadata metadata) { 34 | return new ArgbImageWithMetadata(this, metadata); 35 | } 36 | 37 | default ArgbImage withDefaultMetadata() { 38 | return withMetadata(new PNGMetadata() 39 | .setAuthor() 40 | .setCreationTime()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/glavo/png/image/ArgbImageBuffer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Glavo 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.glavo.png.image; 17 | 18 | public final class ArgbImageBuffer implements ArgbImage { 19 | private final int width; 20 | private final int height; 21 | private final int[] colors; 22 | 23 | public ArgbImageBuffer(int width, int height) { 24 | if (width <= 0 || height <= 0) { 25 | throw new IllegalArgumentException(); 26 | } 27 | 28 | this.width = width; 29 | this.height = height; 30 | this.colors = new int[width * height]; 31 | } 32 | 33 | @Override 34 | public int getWidth() { 35 | return width; 36 | } 37 | 38 | @Override 39 | public int getHeight() { 40 | return height; 41 | } 42 | 43 | @Override 44 | public int getArgb(int x, int y) { 45 | if (x < 0 || x >= width || y < 0 || y >= height) { 46 | throw new IllegalArgumentException(); 47 | } 48 | 49 | return colors[x + y * width]; 50 | } 51 | 52 | public void setArgb(int x, int y, int color) { 53 | if (x < 0 || x >= width || y < 0 || y >= height) { 54 | throw new IllegalArgumentException(); 55 | } 56 | 57 | colors[x + y * width] = color; 58 | } 59 | 60 | public void setArgb(int x, int y, int a, int r, int g, int b) { 61 | setArgb(x, y, (a << 24) | (r << 16) | (g << 8) | b); 62 | } 63 | 64 | public void setRgb(int x, int y, int r, int g, int b) { 65 | setArgb(x, y, 0xff, r, g, b); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/org/glavo/png/image/ArgbImageWithMetadata.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Glavo 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.glavo.png.image; 17 | 18 | import org.glavo.png.PNGMetadata; 19 | 20 | final class ArgbImageWithMetadata implements ArgbImage { 21 | private final ArgbImage source; 22 | private final PNGMetadata metadata; 23 | 24 | ArgbImageWithMetadata(ArgbImage source, PNGMetadata metadata) { 25 | this.source = source; 26 | this.metadata = metadata; 27 | } 28 | 29 | @Override 30 | public int getWidth() { 31 | return source.getWidth(); 32 | } 33 | 34 | @Override 35 | public int getHeight() { 36 | return source.getHeight(); 37 | } 38 | 39 | @Override 40 | public int getArgb(int x, int y) { 41 | return source.getArgb(x, y); 42 | } 43 | 44 | @Override 45 | public PNGMetadata getMetadata() { 46 | return metadata; 47 | } 48 | 49 | @Override 50 | public ArgbImage withMetadata(PNGMetadata metadata) { 51 | return new ArgbImageWithMetadata(source, metadata); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/glavo/png/image/ArgbImageWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Glavo 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.glavo.png.image; 17 | 18 | import java.util.Objects; 19 | 20 | public abstract class ArgbImageWrapper implements ArgbImage { 21 | protected final T image; 22 | protected final int width; 23 | protected final int height; 24 | 25 | protected ArgbImageWrapper(T image, int width, int height) { 26 | this.image = image; 27 | this.width = width; 28 | this.height = height; 29 | 30 | if (width <= 0 || height <= 0) { 31 | throw new IllegalArgumentException("Invalid picture size"); 32 | } 33 | } 34 | 35 | public T getImage() { 36 | return image; 37 | } 38 | 39 | @Override 40 | public int getWidth() { 41 | return width; 42 | } 43 | 44 | @Override 45 | public int getHeight() { 46 | return height; 47 | } 48 | 49 | public static ArgbImageWrapper of(T image, int width, int height, ColorExtractor extractor) { 50 | Objects.requireNonNull(extractor); 51 | return new ArgbImageWrapper(image, width, height) { 52 | @Override 53 | public int getArgb(int x, int y) { 54 | return extractor.getArgb(super.image, x, y); 55 | } 56 | }; 57 | } 58 | 59 | @FunctionalInterface 60 | public interface ColorExtractor { 61 | int getArgb(T image, int x, int y); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/org/glavo/png/BasicTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Glavo 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.glavo.png; 17 | 18 | import org.glavo.png.image.ArgbImage; 19 | import org.junit.jupiter.params.ParameterizedTest; 20 | import org.junit.jupiter.params.provider.MethodSource; 21 | 22 | import javax.imageio.ImageIO; 23 | import java.awt.image.BufferedImage; 24 | import java.io.ByteArrayInputStream; 25 | import java.io.ByteArrayOutputStream; 26 | import java.io.IOException; 27 | import java.io.InputStream; 28 | import java.net.URISyntaxException; 29 | import java.nio.file.Files; 30 | import java.nio.file.Path; 31 | import java.util.Arrays; 32 | import java.util.stream.Stream; 33 | import java.util.zip.Deflater; 34 | 35 | import static org.junit.jupiter.api.Assertions.assertEquals; 36 | 37 | public interface BasicTest { 38 | record Argument(Path file, PNGType pngType, int compressLevel) { 39 | } 40 | 41 | String defaultTestFile = "minecraft.png"; 42 | String[] testFiles = { 43 | defaultTestFile, 44 | "skin.png", "steve.png", "alex.png", 45 | "background.jpg", 46 | "rgba.png" 47 | }; 48 | 49 | static Stream testFiles() { 50 | return Arrays.stream(testFiles) 51 | .map(path -> { 52 | try { 53 | return Path.of(BasicTest.class.getResource(path).toURI()); 54 | } catch (URISyntaxException e) { 55 | throw new RuntimeException(e); 56 | } 57 | }) 58 | .flatMap(file -> Stream.of( 59 | new Argument(file, PNGType.RGBA, Deflater.DEFAULT_COMPRESSION), 60 | new Argument(file, PNGType.RGBA, 0), 61 | new Argument(file, PNGType.RGBA, 1), 62 | new Argument(file, PNGType.RGBA, 9), 63 | 64 | new Argument(file, PNGType.RGB, Deflater.DEFAULT_COMPRESSION), 65 | new Argument(file, PNGType.RGB, 0), 66 | new Argument(file, PNGType.RGB, 1), 67 | new Argument(file, PNGType.RGB, 9) 68 | )); 69 | } 70 | 71 | ArgbImage readImage(InputStream input) throws IOException; 72 | 73 | @ParameterizedTest 74 | @MethodSource("testFiles") 75 | default void testWriteArgbFile(Argument arg) throws Exception { 76 | ArgbImage sourceImage; 77 | try (InputStream input = Files.newInputStream(arg.file())) { 78 | sourceImage = readImage(input); 79 | } 80 | BufferedImage targetImage; 81 | 82 | ByteArrayOutputStream temp = new ByteArrayOutputStream(); 83 | try (PNGWriter writer = new PNGWriter(temp, arg.pngType(), arg.compressLevel())) { 84 | writer.write(sourceImage); 85 | } 86 | targetImage = ImageIO.read(new ByteArrayInputStream(temp.toByteArray())); 87 | 88 | int width = sourceImage.getWidth(); 89 | int height = sourceImage.getHeight(); 90 | 91 | assertEquals(width, targetImage.getWidth()); 92 | assertEquals(height, targetImage.getHeight()); 93 | 94 | for (int x = 0; x < width; x++) { 95 | for (int y = 0; y < height; y++) { 96 | int sourceColor = sourceImage.getArgb(x, y); 97 | int targetColor = targetImage.getRGB(x, y); 98 | 99 | assertEquals((byte) (sourceColor >>> 16), (byte) (targetColor >>> 16)); // Red 100 | assertEquals((byte) (sourceColor >>> 8), (byte) (targetColor >>> 8)); // Green 101 | assertEquals((byte) (sourceColor >>> 0), (byte) (targetColor >>> 0)); // Blue 102 | 103 | // Alpha 104 | if (arg.pngType() != PNGType.RGB) { 105 | assertEquals((byte) (sourceColor >>> 24), (byte) (targetColor >>> 24)); 106 | } else { 107 | assertEquals((byte) 0xFF, (byte) (targetColor >>> 24)); 108 | } 109 | } 110 | } 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/test/java/org/glavo/png/JavaFXTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Glavo 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.glavo.png; 17 | 18 | import javafx.application.Platform; 19 | import javafx.scene.image.Image; 20 | import javafx.scene.image.PixelReader; 21 | import org.glavo.png.image.ArgbImage; 22 | import org.glavo.png.javafx.PNGJavaFXUtils; 23 | import org.junit.jupiter.api.condition.DisabledIf; 24 | import org.junit.jupiter.params.ParameterizedTest; 25 | import org.junit.jupiter.params.provider.MethodSource; 26 | 27 | import java.io.ByteArrayInputStream; 28 | import java.io.IOException; 29 | import java.io.InputStream; 30 | import java.nio.file.Files; 31 | 32 | import static org.junit.jupiter.api.Assertions.assertEquals; 33 | 34 | @DisabledIf("isDisabled") 35 | public class JavaFXTest implements BasicTest { 36 | 37 | private static boolean disabled = true; 38 | 39 | static { 40 | try { 41 | Platform.startup(() -> { 42 | }); 43 | 44 | disabled = false; 45 | } catch (Throwable e) { 46 | e.printStackTrace(); 47 | } 48 | } 49 | 50 | public static boolean isDisabled() { 51 | return disabled; 52 | } 53 | 54 | @Override 55 | public ArgbImage readImage(InputStream input) throws IOException { 56 | return PNGJavaFXUtils.asArgbImage(new Image(input)); 57 | } 58 | 59 | @ParameterizedTest 60 | @MethodSource("testFiles") 61 | public void writeImageToArrayTest(Argument arg) throws IOException { 62 | Image sourceImage; 63 | try (InputStream input = Files.newInputStream(arg.file())) { 64 | sourceImage = new Image(input); 65 | } 66 | 67 | byte[] data = PNGJavaFXUtils.writeImageToArray(sourceImage, arg.pngType(), arg.compressLevel()); 68 | Image targetImage = new Image(new ByteArrayInputStream(data)); 69 | 70 | 71 | assertEquals(sourceImage.getWidth(), targetImage.getWidth()); 72 | assertEquals(sourceImage.getHeight(), targetImage.getHeight()); 73 | 74 | int width = (int) sourceImage.getWidth(); 75 | int height = (int) sourceImage.getHeight(); 76 | 77 | PixelReader sourceReader = sourceImage.getPixelReader(); 78 | PixelReader targetReader = targetImage.getPixelReader(); 79 | 80 | for (int x = 0; x < width; x++) { 81 | for (int y = 0; y < height; y++) { 82 | int sourceColor = sourceReader.getArgb(x, y); 83 | int targetColor = targetReader.getArgb(x, y); 84 | 85 | assertEquals((byte) (sourceColor >>> 16), (byte) (targetColor >>> 16)); // Red 86 | assertEquals((byte) (sourceColor >>> 8), (byte) (targetColor >>> 8)); // Green 87 | assertEquals((byte) (sourceColor >>> 0), (byte) (targetColor >>> 0)); // Blue 88 | 89 | // Alpha 90 | if (arg.pngType() != PNGType.RGB) { 91 | assertEquals((byte) (sourceColor >>> 24), (byte) (targetColor >>> 24)); 92 | } else { 93 | assertEquals((byte) 0xFF, (byte) (targetColor >>> 24)); 94 | } 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/test/java/org/glavo/png/PNGWriterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Glavo 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.glavo.png; 17 | 18 | import org.apache.commons.imaging.ImageReadException; 19 | import org.apache.commons.imaging.common.ImageMetadata; 20 | import org.apache.commons.imaging.formats.png.PngImageParser; 21 | import org.glavo.png.image.ArgbImage; 22 | import org.glavo.png.imageio.PNGImageIOUtils; 23 | import org.junit.jupiter.api.Test; 24 | 25 | import javax.imageio.ImageIO; 26 | import java.io.ByteArrayOutputStream; 27 | import java.io.IOException; 28 | import java.io.InputStream; 29 | import java.time.LocalDateTime; 30 | 31 | import static org.junit.jupiter.api.Assertions.assertNotNull; 32 | 33 | public class PNGWriterTest implements BasicTest { 34 | 35 | @Test 36 | public void testMetadata() throws IOException, ImageReadException { 37 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 38 | try (PNGWriter writer = new PNGWriter(out)) { 39 | PNGMetadata metadata = new PNGMetadata() 40 | .setCreationTime(LocalDateTime.of(2023, 1, 12, 12, 0)) 41 | .setAuthor("Glavo"); 42 | writer.write(PNGImageIOUtils.asArgbImage(ImageIO.read(PNGWriterTest.class.getResource(BasicTest.defaultTestFile))) 43 | .withMetadata(metadata)); 44 | } 45 | 46 | byte[] image = out.toByteArray(); 47 | ImageMetadata metadata = new PngImageParser().getMetadata(image); 48 | assertNotNull(metadata); 49 | } 50 | 51 | @Override 52 | public ArgbImage readImage(InputStream input) throws IOException { 53 | return PNGImageIOUtils.asArgbImage(ImageIO.read(input)); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/org/glavo/png/example/Gradient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Glavo 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.glavo.png.example; 17 | 18 | import org.glavo.png.PNGWriter; 19 | import org.glavo.png.image.ArgbImageBuffer; 20 | 21 | import java.io.IOException; 22 | import java.nio.file.Files; 23 | import java.nio.file.Paths; 24 | 25 | public class Gradient { 26 | public static void main(String[] args) throws IOException { 27 | ArgbImageBuffer buffer = new ArgbImageBuffer(250, 250); 28 | for (int x = 0; x < 250; x++) { 29 | for (int y = 0; y < 250; y++) { 30 | buffer.setArgb(x, y, 255 - ((x / 2) & 255), x & 255, y & 255, 128); 31 | } 32 | } 33 | 34 | try (PNGWriter writer = new PNGWriter(Files.newOutputStream(Paths.get("gradient.png")))) { 35 | writer.write(buffer); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/org/glavo/png/example/Transparent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Glavo 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.glavo.png.example; 17 | 18 | import org.glavo.png.PNGWriter; 19 | import org.glavo.png.image.ArgbImage; 20 | 21 | import java.io.IOException; 22 | import java.nio.file.Files; 23 | import java.nio.file.Paths; 24 | 25 | public class Transparent { 26 | public static void main(String[] args) throws IOException { 27 | try (PNGWriter writer = new PNGWriter(Files.newOutputStream(Paths.get("transparent.png")))) { 28 | writer.write(new ArgbImage() { 29 | @Override 30 | public int getWidth() { 31 | return 960; 32 | } 33 | 34 | @Override 35 | public int getHeight() { 36 | return 540; 37 | } 38 | 39 | @Override 40 | public int getArgb(int x, int y) { 41 | return 0; 42 | } 43 | }); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/resources/org/glavo/png/alex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Glavo/SimplePNG/c0c9c5ee17d5d3ba6a4f8c3e02de883029bc4134/src/test/resources/org/glavo/png/alex.png -------------------------------------------------------------------------------- /src/test/resources/org/glavo/png/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Glavo/SimplePNG/c0c9c5ee17d5d3ba6a4f8c3e02de883029bc4134/src/test/resources/org/glavo/png/background.jpg -------------------------------------------------------------------------------- /src/test/resources/org/glavo/png/minecraft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Glavo/SimplePNG/c0c9c5ee17d5d3ba6a4f8c3e02de883029bc4134/src/test/resources/org/glavo/png/minecraft.png -------------------------------------------------------------------------------- /src/test/resources/org/glavo/png/rgba.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Glavo/SimplePNG/c0c9c5ee17d5d3ba6a4f8c3e02de883029bc4134/src/test/resources/org/glavo/png/rgba.png -------------------------------------------------------------------------------- /src/test/resources/org/glavo/png/skin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Glavo/SimplePNG/c0c9c5ee17d5d3ba6a4f8c3e02de883029bc4134/src/test/resources/org/glavo/png/skin.png -------------------------------------------------------------------------------- /src/test/resources/org/glavo/png/steve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Glavo/SimplePNG/c0c9c5ee17d5d3ba6a4f8c3e02de883029bc4134/src/test/resources/org/glavo/png/steve.png --------------------------------------------------------------------------------