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