com.badlogic.gdx.graphics.PixmapIO Maven / Gradle / Ivy
/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.badlogic.gdx.graphics;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.zip.CRC32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.utils.ByteArray;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.StreamUtils;
/** Writes Pixmaps to various formats.
* @author mzechner
* @author Nathan Sweet */
public class PixmapIO {
/** Writes the {@link Pixmap} to the given file using a custom compression scheme. First three integers define the width, height
* and format, remaining bytes are zlib compressed pixels. To be able to load the Pixmap to a Texture, use ".cim" as the file
* suffix. Throws a GdxRuntimeException in case the Pixmap couldn't be written to the file.
* @param file the file to write the Pixmap to */
static public void writeCIM (FileHandle file, Pixmap pixmap) {
CIM.write(file, pixmap);
}
/** Reads the {@link Pixmap} from the given file, assuming the Pixmap was written with the
* {@link PixmapIO#writeCIM(FileHandle, Pixmap)} method. Throws a GdxRuntimeException in case the file couldn't be read.
* @param file the file to read the Pixmap from */
static public Pixmap readCIM (FileHandle file) {
return CIM.read(file);
}
/** Writes the pixmap as a PNG with compression. See {@link PNG} to configure the compression level, more efficiently flip the
* pixmap vertically, and to write out multiple PNGs with minimal allocation. */
static public void writePNG (FileHandle file, Pixmap pixmap) {
try {
PNG writer = new PNG((int)(pixmap.getWidth() * pixmap.getHeight() * 1.5f)); // Guess at deflated size.
try {
writer.setFlipY(false);
writer.write(file, pixmap);
} finally {
writer.dispose();
}
} catch (IOException ex) {
throw new GdxRuntimeException("Error writing PNG: " + file, ex);
}
}
/** @author mzechner */
static private class CIM {
static private final int BUFFER_SIZE = 32000;
static private final byte[] writeBuffer = new byte[BUFFER_SIZE];
static private final byte[] readBuffer = new byte[BUFFER_SIZE];
static public void write (FileHandle file, Pixmap pixmap) {
DataOutputStream out = null;
try {
// long start = System.nanoTime();
DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(file.write(false));
out = new DataOutputStream(deflaterOutputStream);
out.writeInt(pixmap.getWidth());
out.writeInt(pixmap.getHeight());
out.writeInt(Format.toGdx2DPixmapFormat(pixmap.getFormat()));
ByteBuffer pixelBuf = pixmap.getPixels();
pixelBuf.position(0);
pixelBuf.limit(pixelBuf.capacity());
int remainingBytes = pixelBuf.capacity() % BUFFER_SIZE;
int iterations = pixelBuf.capacity() / BUFFER_SIZE;
synchronized (writeBuffer) {
for (int i = 0; i < iterations; i++) {
pixelBuf.get(writeBuffer);
out.write(writeBuffer);
}
pixelBuf.get(writeBuffer, 0, remainingBytes);
out.write(writeBuffer, 0, remainingBytes);
}
pixelBuf.position(0);
pixelBuf.limit(pixelBuf.capacity());
// Gdx.app.log("PixmapIO", "write (" + file.name() + "):" + (System.nanoTime() - start) / 1000000000.0f + ", " +
// Thread.currentThread().getName());
} catch (Exception e) {
throw new GdxRuntimeException("Couldn't write Pixmap to file '" + file + "'", e);
} finally {
StreamUtils.closeQuietly(out);
}
}
static public Pixmap read (FileHandle file) {
DataInputStream in = null;
try {
// long start = System.nanoTime();
in = new DataInputStream(new InflaterInputStream(new BufferedInputStream(file.read())));
int width = in.readInt();
int height = in.readInt();
Format format = Format.fromGdx2DPixmapFormat(in.readInt());
Pixmap pixmap = new Pixmap(width, height, format);
ByteBuffer pixelBuf = pixmap.getPixels();
pixelBuf.position(0);
pixelBuf.limit(pixelBuf.capacity());
synchronized (readBuffer) {
int readBytes = 0;
while ((readBytes = in.read(readBuffer)) > 0) {
pixelBuf.put(readBuffer, 0, readBytes);
}
}
pixelBuf.position(0);
pixelBuf.limit(pixelBuf.capacity());
// Gdx.app.log("PixmapIO", "read:" + (System.nanoTime() - start) / 1000000000.0f);
return pixmap;
} catch (Exception e) {
throw new GdxRuntimeException("Couldn't read Pixmap from file '" + file + "'", e);
} finally {
StreamUtils.closeQuietly(in);
}
}
}
/** PNG encoder with compression. An instance can be reused to encode multiple PNGs with minimal allocation.
*
*
* Copyright (c) 2007 Matthias Mann - www.matthiasmann.de
* Copyright (c) 2014 Nathan Sweet
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author Matthias Mann
* @author Nathan Sweet */
static public class PNG implements Disposable {
static private final byte[] SIGNATURE = {(byte)137, 80, 78, 71, 13, 10, 26, 10};
static private final int IHDR = 0x49484452, IDAT = 0x49444154, IEND = 0x49454E44;
static private final byte COLOR_ARGB = 6;
static private final byte COMPRESSION_DEFLATE = 0;
static private final byte FILTER_NONE = 0;
static private final byte INTERLACE_NONE = 0;
static private final byte PAETH = 4;
private final ChunkBuffer buffer;
private final DeflaterOutputStream deflaterOutput;
private final Deflater deflater;
private ByteArray lineOutBytes, curLineBytes, prevLineBytes;
private boolean flipY = true;
private int lastLineLen;
public PNG () {
this(128 * 128);
}
public PNG (int initialBufferSize) {
buffer = new ChunkBuffer(initialBufferSize);
deflater = new Deflater();
deflaterOutput = new DeflaterOutputStream(buffer, deflater);
}
/** If true, the resulting PNG is flipped vertically. Default is true. */
public void setFlipY (boolean flipY) {
this.flipY = flipY;
}
/** Sets the deflate compression level. Default is {@link Deflater#DEFAULT_COMPRESSION}. */
public void setCompression (int level) {
deflater.setLevel(level);
}
public void write (FileHandle file, Pixmap pixmap) throws IOException {
OutputStream output = file.write(false);
try {
write(output, pixmap);
} finally {
StreamUtils.closeQuietly(output);
}
}
/** Writes the pixmap to the stream without closing the stream. */
public void write (OutputStream output, Pixmap pixmap) throws IOException {
DataOutputStream dataOutput = new DataOutputStream(output);
dataOutput.write(SIGNATURE);
buffer.writeInt(IHDR);
buffer.writeInt(pixmap.getWidth());
buffer.writeInt(pixmap.getHeight());
buffer.writeByte(8); // 8 bits per component.
buffer.writeByte(COLOR_ARGB);
buffer.writeByte(COMPRESSION_DEFLATE);
buffer.writeByte(FILTER_NONE);
buffer.writeByte(INTERLACE_NONE);
buffer.endChunk(dataOutput);
buffer.writeInt(IDAT);
deflater.reset();
int lineLen = pixmap.getWidth() * 4;
byte[] lineOut, curLine, prevLine;
if (lineOutBytes == null) {
lineOut = (lineOutBytes = new ByteArray(lineLen)).items;
curLine = (curLineBytes = new ByteArray(lineLen)).items;
prevLine = (prevLineBytes = new ByteArray(lineLen)).items;
} else {
lineOut = lineOutBytes.ensureCapacity(lineLen);
curLine = curLineBytes.ensureCapacity(lineLen);
prevLine = prevLineBytes.ensureCapacity(lineLen);
for (int i = 0, n = lastLineLen; i < n; i++)
prevLine[i] = 0;
}
lastLineLen = lineLen;
ByteBuffer pixels = pixmap.getPixels();
int oldPosition = pixels.position();
boolean rgba8888 = pixmap.getFormat() == Format.RGBA8888;
for (int y = 0, h = pixmap.getHeight(); y < h; y++) {
int py = flipY ? (h - y - 1) : y;
if (rgba8888) {
pixels.position(py * lineLen);
pixels.get(curLine, 0, lineLen);
} else {
for (int px = 0, x = 0; px < pixmap.getWidth(); px++) {
int pixel = pixmap.getPixel(px, py);
curLine[x++] = (byte)((pixel >> 24) & 0xff);
curLine[x++] = (byte)((pixel >> 16) & 0xff);
curLine[x++] = (byte)((pixel >> 8) & 0xff);
curLine[x++] = (byte)(pixel & 0xff);
}
}
lineOut[0] = (byte)(curLine[0] - prevLine[0]);
lineOut[1] = (byte)(curLine[1] - prevLine[1]);
lineOut[2] = (byte)(curLine[2] - prevLine[2]);
lineOut[3] = (byte)(curLine[3] - prevLine[3]);
for (int x = 4; x < lineLen; x++) {
int a = curLine[x - 4] & 0xff;
int b = prevLine[x] & 0xff;
int c = prevLine[x - 4] & 0xff;
int p = a + b - c;
int pa = p - a;
if (pa < 0) pa = -pa;
int pb = p - b;
if (pb < 0) pb = -pb;
int pc = p - c;
if (pc < 0) pc = -pc;
if (pa <= pb && pa <= pc)
c = a;
else if (pb <= pc) //
c = b;
lineOut[x] = (byte)(curLine[x] - c);
}
deflaterOutput.write(PAETH);
deflaterOutput.write(lineOut, 0, lineLen);
byte[] temp = curLine;
curLine = prevLine;
prevLine = temp;
}
pixels.position(oldPosition);
deflaterOutput.finish();
buffer.endChunk(dataOutput);
buffer.writeInt(IEND);
buffer.endChunk(dataOutput);
output.flush();
}
/** Disposal will happen automatically in {@link #finalize()} but can be done explicitly if desired. */
public void dispose () {
deflater.end();
}
static class ChunkBuffer extends DataOutputStream {
final ByteArrayOutputStream buffer;
final CRC32 crc;
ChunkBuffer (int initialSize) {
this(new ByteArrayOutputStream(initialSize), new CRC32());
}
private ChunkBuffer (ByteArrayOutputStream buffer, CRC32 crc) {
super(new CheckedOutputStream(buffer, crc));
this.buffer = buffer;
this.crc = crc;
}
public void endChunk (DataOutputStream target) throws IOException {
flush();
target.writeInt(buffer.size() - 4);
buffer.writeTo(target);
target.writeInt((int)crc.getValue());
buffer.reset();
crc.reset();
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy