Извлечение файлов PNG из APNG в Java
Я пытался извлечь все файлы PNG из файла APNG. Я искал помощи, и хорошо не так много. Все, что я мог найти, это библиотека с открытым исходным кодом pngj, и с его помощью я смог получить первый кадр файла APNG.
Вот код, который я использую
public static void mirror(File orig, File dest, boolean overwrite)
{
PngReader pngr = FileHelper.createPngReader(orig);
PngWriter pngw = FileHelper.createPngWriter(dest, pngr.imgInfo,
overwrite);
pngw.setFilterType(FilterType.FILTER_CYCLIC); // just to test all
// filters
int copyPolicy = ChunkCopyBehaviour.COPY_ALL;
pngw.copyChunksFirst(pngr, copyPolicy);
ImageLine lout = new ImageLine(pngw.imgInfo);
int cols = pngr.imgInfo.cols;
int channels = pngr.imgInfo.channels;
int[] line = new int[cols * channels];
int aux;
for (int row = 0; row < pngr.imgInfo.rows; row++) {
ImageLine l1 = pngr.readRow(row);
line = l1.unpack(line, false);
for (int c1 = 0, c2 = cols - 1; c1 < c2; c1++, c2--) {
for (int i = 0; i < channels; i++) {
aux = line[c1 * channels + i];
line[c1 * channels + i] = line[c2 * channels + i];
line[c2 * channels + i] = aux;
}
}
lout.pack(line, false);
pngw.writeRow(lout, row);
}
pngr.end();
pngw.copyChunksLast(pngr, copyPolicy);
pngw.end();
// // print unknown chunks, just for information
List<PngChunk> u = ChunkHelper.filterList(pngr.getChunksList()
.getChunks(), new ChunkPredicate() {
public boolean match(PngChunk c) {
return ChunkHelper.isUnknown(c);
}
});
if (!u.isEmpty())
System.out.println("Unknown chunks:" + u);
}
так что в основном я просто зеркально отражаю файл apng, и он преобразуется в файл png, который является первым кадром. Так может кто-нибудь сказать мне, как получить оставшиеся кадры и сохранить их в виде файлов PNG? Любая помощь или советы будут оценены
2 ответа
На самом деле библиотека PNGJ (я автор) не поддерживает стандарт APNG (отступление: я отказался от него, потому что мне не нравился подход APGN и потому что он не соответствовал идее моей библиотеки: загрузка "огромных" данных -IDAT- последовательно, построчно и загрузка фрагментов (метаданных) непосредственно в память; APGN использует стандарт PGN, сохраняя кадры в фрагментах).
Вы можете попытаться использовать его в любом случае, чтобы делать то, что вы хотите, но не элегантно и надежно. Вот пример. Помимо уродливости, это не будет работать для частичных кадров (кадров, меньших, чем полное изображение, которое поддерживает APNG), ни для наложений, ни для палитровых изображений (более поздняя проблема будет легче всего исправить) (исправлена, мы надеемся).
Это было проверено с http://philip.html5.org/tests/apng/028.png
import java.io.File;
import java.io.FileOutputStream;
import ar.com.hjg.pngj.FileHelper;
import ar.com.hjg.pngj.ImageLine;
import ar.com.hjg.pngj.PngHelperInternal;
import ar.com.hjg.pngj.PngReader;
import ar.com.hjg.pngj.PngWriter;
import ar.com.hjg.pngj.chunks.*;
public class ApngSplit {
private static final String PREFIX = "apngf";
/** reads a APNG file and tries to split it into its frames */
public static void process(File orig) throws Exception {
PngReader pngr = FileHelper.createPngReader(orig);
File dest = new File(orig.getParent(), PREFIX + "0_" + orig.getName());
PngWriter pngw = FileHelper.createPngWriter(dest, pngr.imgInfo, true);
System.out.println("writing default frame " + pngw.getFilename());
pngr.setChunkLoadBehaviour(ChunkLoadBehaviour.LOAD_CHUNK_ALWAYS);
pngr.setMaxBytesMetadata(Integer.MAX_VALUE);
pngr.setMaxTotalBytesRead(Long.MAX_VALUE);
pngr.setSkipChunkIds(new String[] {});
int copyPolicy = ChunkCopyBehaviour.COPY_PALETTE | ChunkCopyBehaviour.COPY_ALL_SAFE;
pngw.copyChunksFirst(pngr, copyPolicy);
int cols = pngr.imgInfo.cols;
int channels = pngr.imgInfo.channels;
for (int row = 0; row < pngr.imgInfo.rows; row++) {
ImageLine l1 = pngr.readRow(row);
pngw.writeRow(l1, row);
}
pngr.end();
pngw.copyChunksLast(pngr, copyPolicy);
pngw.end();
processExtra2(orig, pngr.getChunksList());
}
private static void processExtra2(File orig, ChunksList chunks) throws Exception {
int numframe = 0;
FileOutputStream os = null;
boolean afterIdat = false;
for (PngChunk chunkApng : chunks.getChunks()) {
if (chunkApng.id.equals("IDAT"))
afterIdat = true;
if (chunkApng.id.equals("fcTL") && afterIdat) {
numframe++;
if (os != null)
endPng(chunks, os);
File dest = new File(orig.getParent(), PREFIX + numframe + "_" + orig.getName());
System.out.println("writing seq " + numframe + " : " + dest);
os = new FileOutputStream(dest);
beginPng(chunks, os);
}
if (chunkApng.id.equals("fdAT")) {
ChunkRaw crawf = chunkApng.createRawChunk();
int seq = PngHelperInternal.readInt4fromBytes(crawf.data, 0);
ChunkRaw crawi = new ChunkRaw(crawf.len - 4, ChunkHelper.b_IDAT, true);
System.arraycopy(crawf.data, 4, crawi.data, 0, crawi.data.length);
crawi.writeChunk(os);
}
}
if (os != null)
endPng(chunks, os);
}
private static void endPng(ChunksList chunks, FileOutputStream fos) throws Exception {
chunks.getById1(PngChunkIEND.ID).createRawChunk().writeChunk(fos);
fos.close();
}
private static void beginPng(ChunksList chunks, FileOutputStream fos) throws Exception {
fos.write(new byte[] { -119, 80, 78, 71, 13, 10, 26, 10 }); // signature
chunks.getById1(PngChunkIHDR.ID).createRawChunk().writeChunk(fos);
PngChunk plte = chunks.getById1(PngChunkPLTE.ID);
if (plte != null)
plte.createRawChunk().writeChunk(fos);
}
public static void main(String[] args) throws Exception {
process(new File("C:/temp/029.png"));
}
}
Некоторые ресурсы, которые могут помочь:
Эти примеры показывают, как читать файлы apng, но это C и использование libpng: https://sourceforge.net/projects/apng/files/libpng/examples/
Вот некоторый Java-код, но он может только создавать файлы APNG, но не читать их: https://www.reto-hoehener.ch/japng/index.html
Вот код JavaScript для чтения и отображения файлов APNG: https://github.com/davidmz/apng-canvas