(Ergänzung: Bitte verzeihen Sie Tippfehler)
** 8/24 Hinzugefügt, weil ein kritischer Fehler aufgetreten ist **
encoder.setInput(buffer);
encoder.finish(); //Dies ist erforderlich
Einzelheiten entnehmen Sie bitte den PNG-Spezifikationen. Es werden auch Bilder mit einer Palette von 256 Farben und Graustufen unterstützt. Da es sich bei den meisten tatsächlich verwendeten Bildern jedoch um RGB-Vollfarbbilder mit 24 Bit handelt (die auch 48 Bit unterstützen), werde ich sie dieses Mal ignorieren.
Das Bild wird von javafx.scene.image.Image gelesen (weil es einfach ist)
Enthält zunächst eine 6-stellige Signatur.
static public byte[] getPNGSignature () {
return new byte[]{(byte)0x89, (byte)0x50, (byte)0x4E, (byte)0x47,
(byte)0x0D, (byte)0x0A, (byte)0x1A, (byte)0x0A};
}
Wenn Sie es gewaltsam öffnen, können Sie .PNG ... usw. lesen. Schreiben Sie dies zuerst in eine Datei.
PNG legt Daten separat in einer Box namens Chunk ab. Die Daten werden in Big Endian gespeichert (das Gegenteil ist der Fall, da die Intel-CPU Littel Endian ist).
Es hat das folgende Format.
4-Byte-Blockgröße 4-Byte-Blockname (4 alphanumerische Zeichen) ..... ..... Daten zur Blockgröße ..... 4 Byte CRC32 (bis zu Blockname + Blockdaten)
Wenn Sie die Spezifikationen lesen, werden Sie viele davon sehen, aber Sie können sie fast ignorieren, so dass drei wesentlich sind.
IHDR-Bildheader IDAT-Bildkörperdaten Am Ende der IEND-Datei anhängen
PLTE wird nicht benötigt, da es keine Palette hat. IHDR hat eine feste Blockgröße von 13 und IEND hat eine feste Blockgröße von 0. Da IEND keine Daten hat, ist IEND immer dasselbe wie CRC.
IHDR ist wie folgt Horizontale Größe (Breite) des 4-Byte-Bildes Vertikale Größe (Höhe) des 4-Byte-Bildes 1 Byte Bittiefe (8 oder 16 für Vollfarbe) 1-Byte-Kolortyp (2 oder 6 6 = mit Alphakanal für Vollfarbe) 1-Byte-Komprimierungsalgorithmus (da nur 0 = 1 Typ vorhanden ist) 1 Byte Filter (0 ohne) 1 Byte Interlace (0 ohne)
Dieses Mal sind weder der Alphakanal noch der Filter bereit, Interlacing zu unterstützen, also Breite, Höhe, 8,2,0,0,0.
Das Schreiben in HEX sieht so aus.
00 00 00 0D 49 48 44 52 13 IHDR XX XX XX XX YY YY YY YY width height 08 02 00 00 00 CC CC CC 8-Bit-Vollfarbe CC = CRC CC
Erstellen Sie zunächst die Daten für die Komprimierung. 24bit Vollfarbe FRGBRGBRGB ... FRGBRGBRGB ...
Es wird sein. Das erste F ist umständlich, aber wenn ich mir die Spezifikationen ansehe, heißt es, dass 1 Byte für den Filter am Anfang der Zeile erforderlich ist. Geben Sie 0 ein, da es nicht gefiltert wird.
Sie müssen lediglich den damit erstellten Puffer in den Deflater einfügen. Nachdem Sie die Größe erhalten haben, berechnen und schreiben Sie zusätzlich zum Header den CRC.
Es gab eine Stelle, die gelogen hat, dass * Deflater eine schlechtere Komprimierungsrate als LZW hatte, aber sie wurde durch Kombination von LZ77 mit Huffman-Codierung und arithmetischer Codierung verbessert, und die Komprimierungsrate ist im Durchschnitt um 30% oder mehr höher als LZW (das). Langsam stattdessen) *
Es ist ein fester Wert, sodass Sie an nichts denken müssen.
Es ist einfacher, einen Header zu erstellen, und die Komprimierung ist einfacher, als die Bibliothek schlecht zu treffen, da nur Defrater aufgerufen wird (wenn Sie den Header dieses ImageWriter mühsam bearbeiten müssen ...). CRC ist nur eine kleine Neufassung des Beispielcodes in der Spezifikation.
Ich bin mir nicht sicher, ob es sinnvoll ist, Klassen für PNG und PNG Saver zu trennen. Es scheint, dass ich dachte, ich würde später etwas hinzufügen.
import java.util.zip.Deflater;
import javafx.scene.image.Image;
public interface PNG {
static public byte[] getPNGSignature () {
return new byte[]{(byte)0x89, (byte)0x50, (byte)0x4E, (byte)0x47,
(byte)0x0D, (byte)0x0A, (byte)0x1A, (byte)0x0A};
}
static public final int HEADER_SIZE = 4;
public static PNGChunk createIHDR(int width,int height,int bitdepth,int colortype,int filter,int interlace) {
PNGChunk header = new PNGChunk(ChunkTYPE.IHDR);
byte[] buffer = header.getBuffer();
buffer[0] = (byte) (width >>> 24 & 0xff);
buffer[1] = (byte) (width >>> 16 & 0xff);
buffer[2] = (byte) (width >>> 8 & 0xff);
buffer[3] = (byte) (width >>> 0 & 0xff);
buffer[4] = (byte) (height >>> 24 & 0xff);
buffer[5] = (byte) (height >>> 16 & 0xff);
buffer[6] = (byte) (height >>> 8 & 0xff);
buffer[7] = (byte) (height >>> 0 & 0xff);
buffer[8] = (byte) bitdepth;
buffer[9] = (byte) colortype;
buffer[10] = 0; //compress type a
buffer[11] = (byte) filter;
buffer[12] = (byte) interlace;
return header;
}
public static PNGChunk createIHDR(int width,int height) {
return createIHDR(width,height,8,2,0,0);
}
public static PNGChunk createIEND() {
return new PNGChunk(ChunkTYPE.IEND);
}
public static PNGChunk createIDAT(Image img) {
int width = (int) img.getWidth();
int height = (int) img.getHeight();
PNGChunk data = new PNGChunk(ChunkTYPE.IDAT);
int raw = width * 3 + 1; // add Filter Byte(1byte)
byte[] buffer = new byte[raw * height ];
byte[] outbuffer = new byte[raw * height ];
for (int y = 0 ; y < height ; y++) {
int offset = y * raw ;
buffer[offset++] = 0; // scan line first byte is Filter Byte(1byte) /zero because no use filter
for (int x = 0 ; x < width ; x++) {
int color = img.getPixelReader().getArgb(x, y);
buffer[offset++] = (byte) ((color >>> 16) & 0xff); //R
buffer[offset++] = (byte) ((color >>> 8) & 0xff); //G
buffer[offset++] = (byte) ((color >>> 0) & 0xff); //B
}
}
Deflater encoder = new Deflater();
encoder.setInput(buffer);
encoder.finish(); // add Aug 24,2018 MUST USE
int compresslength = encoder.deflate(outbuffer);
data.setBuffer(outbuffer);
data.setLength(compresslength);
return data;
}
}
public class PNGsaver implements PNG {
static public void PNGWriteFile (String outpath,javafx.scene.image.Image img) throws IOException {
File outFile = new File(outpath);
if (! outFile.exists()) {
FileOutputStream outStream = ( new FileOutputStream(outpath));
outStream.write(PNG.getPNGSignature());
PNGChunk header = PNG.createIHDR((int)img.getWidth(),(int)img.getHeight());
header.writeChunk(outStream);
PNGChunk data =PNG.createIDAT(img);
data.writeChunk(outStream);
PNGChunk eof = PNG.createIEND();
eof.writeChunk(outStream);
outStream.close();
} else {
System.err.println("File is already exist.");
}
}
}
Für das Chunk-Management
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
public class PNGChunk {
private ChunkTYPE type;
private int length;
private boolean crcClucFlag = true;
private long crc;
private byte[] buffer = null;
private CRC crcCulc = new CRC();
public PNGChunk(ChunkTYPE chunkType) {
this.setType(chunkType);
switch (chunkType) {
//Must need
case IHDR: // HEADER
this.setLength(13); // always 13
break;
case PLTE: // Color pallet
break;
case IDAT: // Image Data
break;
case IEND: // END of FILE
this.setLength(0); // always ZERO
//0xAE 0x42 0x60 0x82
this.crc = crcCulc.createCRC(null, 0,this.type);
crcClucFlag = false;
break;
// APNG Chunks
case acTL: // Animation Control
break;
case fcTL: // Frame Control
break;
case fdAT: // Frame Data
break;
default:
// must before PLTE and IDAT
// cHRM,
// tRNS,
// gAMA, // Gamma scale
// sRGB, // sRPG
// between PLTE and IDAT
// iCCP,
// bKGD,
// before IDAT
// pHYs,
// hIST,
// non constrains
// tIME, // modify time - only single chunk
// Multiple chunk OK
// sPLT,
// tEXt, // TEXT
// iTXt, // i18n TEXT
// zTXt, // Archived TEXT
}
}
private void setCRC(long crc) {
this.crc = crc;
}
private void setType(ChunkTYPE chunkType) {
this.type = chunkType;
}
public ChunkTYPE getType() {
return type;
}
public void culcCRC() {
this.crc = crcCulc.createCRC(this.getBuffer(), this.getLength(),this.type);
}
public long getCRC() {
if (crcClucFlag) {
this.crc = crcCulc.createCRC(this.getBuffer(), this.getLength(),this.type);
}
return crc;
}
public int getLength() {
return length;
}
public void setLength(int length) {
// cannot SET FIXED SIZE HEADER
this.length = length;
if (this.buffer == null ) {
this.buffer = new byte [(int)length];
}
}
public byte[] getBuffer() {
return buffer;
}
public void setBuffer(byte[] buffer) {
this.buffer = buffer;
}
public byte[] getChunkText() {
return this.type.toString().getBytes();
}
public void writeChunk(OutputStream out) throws IOException {
ByteBuffer buf = ByteBuffer.allocate(4);
out.write(buf.putInt((int)getLength()).array());
out.write(getChunkText());
out.write(getBuffer(), 0, (int)getLength());
buf = ByteBuffer.allocate(4);
out.write(buf.putInt((int)getCRC()).array());
}
}
Recommended Posts