Essayez d'enregistrer les données pouvant être lues par JavaFX au format PNG

Je n'ai pas le temps d'écrire des phrases, donc seulement les points.

(Ajout: veuillez pardonner les erreurs typographiques)

** 24/08 Ajouté car il y avait un bug critique **

  encoder.setInput(buffer);
  encoder.finish();  //Ceci est nécessaire

Veuillez consulter les spécifications PNG quelque part pour plus de détails. Il prend également en charge les images avec une palette de 256 couleurs et une échelle de gris, mais comme la plupart des images réellement utilisées sont des images RVB 24 bits en couleur (qui prennent également en charge 48 bits), je les ignorerai cette fois.

L'image est lue par javafx.scene.image.Image (car c'est facile)

Format d'en-tête

Le premier contient une signature à 6 caractères.

static public byte[] getPNGSignature () {
		return new byte[]{(byte)0x89, (byte)0x50, (byte)0x4E, (byte)0x47, 
				(byte)0x0D,	(byte)0x0A, (byte)0x1A, (byte)0x0A};
}	

Si vous l'ouvrez de force, vous pouvez lire .PNG ... etc. Écrivez d'abord ceci dans un fichier.

Format de bloc

PNG place les données séparément dans une boîte appelée morceau. Les données sont stockées dans Big Endian (l'inverse est vrai car le processeur Intel est Littel Endian)

Il a le format suivant.

Taille de bloc de 4 octets Nom de bloc de 4 octets (4 caractères alphanumériques) ..... ..... Données de taille de bloc .....  CRC32 4 octets (jusqu'au nom du bloc + les données du bloc)

Morceau requis lors de l'enregistrement d'une image couleur

Lorsque vous lirez les spécifications, vous en verrez beaucoup, mais vous pouvez presque les ignorer, donc trois sont essentiels.

En-tête d'image IHDR Données corporelles de l'image IDAT Joindre à la fin du fichier IEND

PLTE n'est pas nécessaire car il n'a pas de palette. IHDR a une taille de bloc fixe de 13 et IEND a une taille de bloc fixe de 0. Comme IEND n'a pas de données, IEND est toujours le même que CRC.

IHDR est le suivant Taille horizontale (largeur) de l'image de 4 octets Taille verticale (hauteur) de l'image de 4 octets 1 octet de profondeur de bit (8 ou 16 pour la couleur) Type de couleur 1 octet (2 ou 6 6 = avec canal alpha pour la couleur) Algorithme de compression 1 octet (car il n'y a que le type 0 = 1) Filtre 1 octet (0 sans) Entrelacement 1 octet (0 sans)

Cette fois, ni le canal alpha ni le filtre ne sont prêts à prendre en charge l'entrelacement, donc c'est largeur, hauteur, 8,2,0,0,0.

L'écriture en HEX ressemble à ceci.

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 bits couleur CC = CRC CC

Créez un IDAT.

Commencez par créer les données pour la compression. 24 bits en couleur FRGBRGBRGB ... FRGBRGBRGB ...

Ce sera. Le premier F est gênant, mais quand je regarde les spécifications, il dit qu'un octet est requis pour le filtre au début de la ligne. Remplissez 0 car il ne sera pas filtré.

Tout ce que vous avez à faire est d'insérer le tampon créé avec lui dans le dégonfleur. Après avoir obtenu la taille, en plus de l'en-tête, calculez et écrivez le CRC.

Il y avait un site qui mentait que * le dégonfleur avait un taux de compression pire que LZW, mais il a été amélioré en combinant LZ77 avec le codage Huffman et le codage arithmétique, et le taux de compression est supérieur à LZW de 30% ou plus en moyenne (que Lent à la place) *

Écrivez END.

C'est une valeur fixe pour que vous n'ayez à penser à rien.

Il est plus facile de créer un en-tête, et la compression est plus facile que de frapper mal la bibliothèque car elle appelle juste Defrater (si vous vous donnez la peine de modifier l'en-tête de cet ImageWriter ...). CRC est juste une petite réécriture de l'exemple de code dans la spécification.

Exemple de code

Je ne sais pas s'il est judicieux de séparer les classes pour PNG et PNG Saver. Il semble que j'ai pensé ajouter quelque chose plus tard.

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.");
		}
	}
}

Pour la gestion des morceaux

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

Essayez d'enregistrer les données pouvant être lues par JavaFX au format PNG
Comment créer une paire de clés d'ecdsa dans un format lisible par Java
Essayez le moteur JavaScript léger «QuickJS» qui peut être intégré dans C / C ++
Un hachage actif qui peut être traité comme des données même s'il n'est pas dans la base de données
Développement Android-Accès WEB (GET) Essayez d'obtenir des données en communiquant avec l'extérieur. ~
Orienté objet qui peut être compris par les fées
Continuation ・ Hash actif qui peut être traité comme des données même s'il n'est pas dans la base de données ~ Affichage
À propos du problème que hidden_field peut être utilisé de manière insensée
J'ai essayé un puzzle qui ne peut être résolu que par les 10% de mauvais ingénieurs
Quatre rangées avec gravité pouvant être jouées sur la console
Vous pouvez résoudre le problème en vous référant aux deux articles !!!
Introduction à Rakefile qui peut être effectuée en 10 minutes environ
Rechercher une instruction Switch qui peut être convertie en une expression Switch
Une collection de modèles dont vous voulez être conscient pour ne pas compliquer le code
Créons un conteneur Docker qui peut SSH à la configuration minimale de CentOS 8
Lisez les données de Shizuoka Prefecture Point Cloud DB avec Java et essayez de détecter la hauteur de l'arbre.