Speichern Sie die Daten, die von JavaFX gelesen werden können, als PNG

Ich habe keine Zeit, Sätze zu schreiben, also nur die Punkte.

(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)

Header-Format

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.

Chunk-Format

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)

Erforderlicher Block beim Speichern des Vollfarbbilds

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 einen IDAT.

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) *

Schreiben Sie END.

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.

Beispielcode

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

Speichern Sie die Daten, die von JavaFX gelesen werden können, als PNG
So erstellen Sie ein Schlüsselpaar von ecdsa in einem Format, das von Java gelesen werden kann
Probieren Sie die leichtgewichtige JavaScript-Engine „QuickJS“ aus, die in C / C ++ integriert werden kann
Ein aktiver Hash, der als Daten behandelt werden kann, auch wenn er nicht in der Datenbank enthalten ist
Android-Entwicklung - WEB-Zugriff (GET) Versuchen Sie, Daten durch Kommunikation mit der Außenwelt abzurufen. ~
Objektorientiert, das von Feen verstanden werden kann
Fortsetzung ・ Aktiver Hash, der als Daten behandelt werden kann, auch wenn er nicht in der Datenbank enthalten ist ~ Anzeige
Über die Sache, dass hidden_field wahnsinnig benutzt werden kann
Ich habe ein Rätsel ausprobiert, das nur von den unteren 10% der schlechten Ingenieure gelöst werden kann
Vier Reihen mit Schwerkraft, die auf der Konsole gespielt werden können
Sie können das Problem lösen, indem Sie sich auf die beiden Artikel beziehen !!!
Einführung in Rakefile, die in ca. 10 Minuten durchgeführt werden kann
Suchen Sie eine Switch-Anweisung, die in einen Switch-Ausdruck konvertiert werden kann
Eine Sammlung von Mustern, die Sie kennen möchten, um den Code nicht zu komplizieren
Erstellen wir einen Docker-Container, der SSH auf die Mindestkonfiguration von CentOS 8 ausführen kann
Lesen Sie die Daten der Shizuoka Prefecture Point Cloud DB mit Java und versuchen Sie, die Baumhöhe zu ermitteln.