Декодирование анимированного изображения с использованием библиотек DLL libwebp и JNA
Мне нужно читать статические и анимированные изображения WebP в Java. Для libwepb не предлагается DLL , поэтому я скачал исходный код и скомпилировал его в командной строке Native Tools для VS 2022 в Windows следующим образом:
..\libwebp-1.2.2>nmake /f Makefile.vc CFG=release-dynamic RTLIBCFG=dynamic OBJDIR=output
Это привело к файлам ,libwebpdecoder.dll
и . я используюjna-5.12.1.jar
для ЮНА. Мне удалось декодировать статическое изображение с помощьюlibwebp.dll
и функцияWebPDecodeRGBA
, однако декодирование анимированного изображения с этим не работает. Из того, что я понимаю, функции демультиплексора вlibwebpdemux.dll
требуются для этого.
Я пытался реализовать WebPAnimDecoder API, но уже застрял на создании декодера:
WebPAnimDecoder* dec = WebPAnimDecoderNew(webp_data, &dec_options);
Из документации это выглядит такdec_options
может быть просто нулевым, чтобы получить значение по умолчанию, но я не уверен, как он хочетwebp_data
. Я пытался реализовать структуру WebPData , но не уверен, что это правильно.
@Structure.FieldOrder({ "bytes", "length" })
public static class WebPData extends Structure {
public byte[] bytes;
public int length;
}
Я получил исключение «Недопустимый доступ к памяти», просто назначив байты напрямуюdata.bytes = rawdata;
. В определении структуры WebP говорится, что «память в байтах должна быть выделена с использованием WebPMalloc() и т. Д.», Поэтому я предполагаю, что это каким-то образом требуется, но я не знаю, как это будет работать, как я могу получить данные в там? То, как это сейчас, вызывает исключение, жалующееся на возвращаемый тип.
Что меня также смущает, так это то, что пример вызываетWebPAnimDecoderNew
, но эта функция не экспортируется, поэтому я не могу получить к ней доступ. Вместо этого есть функция, называемаяWebPAnimDecoderNewInternal
с дополнительным параметром int, который, по-видимому, должен получатьWEBP_DEMUX_ABI_VERSION
постоянный. Я не совсем уверен, каково это значение. Я действительно должен вызывать функцию «Внутренняя»?
Это то, с чем я тестировал (дополнительная информация в некоторых комментариях):
package webptest;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.ptr.IntByReference;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBufferInt;
import java.awt.image.DirectColorModel;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.Hashtable;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
public class WebPDecodingTest {
public static void main(String[] args) throws Exception {
System.setProperty("jna.library.path", "<path>\\lib\\x64");
// This only shows the first image
ImageIcon staticImage = decodeStatic("https://www.gstatic.com/webp/gallery/4.sm.webp");
ImageIcon animatedImage = decodeStatic("https://storage.googleapis.com/downloads.webmproject.org/webp/images/dancing_banana2.lossless.webp");
showImages(staticImage, animatedImage);
// This throws an error (and it's incomplete anyway)
ImageIcon[] animatedImage2 = decodeAnimated("https://storage.googleapis.com/downloads.webmproject.org/webp/images/dancing_banana2.lossless.webp");
showImages(animatedImage2);
}
//==========================
// Animated image
//==========================
/**
* Decode the frames of an animated image. Doesn't work and incomplete.
*/
private static ImageIcon[] decodeAnimated(String url) throws Exception {
byte[] rawData = getBytesFromURL(new URL(url));
System.out.println("Bytes length: "+rawData.length);
/**
* I assume that this is already not correct. In a C example
* (examples/anim_util.c) it uses "WebPDataInit(&webp_data);" first, but
* that function isn't exported. It's in "src/webp/mux_types.h":
*
// Initializes the contents of the 'webp_data' object with default values.
static WEBP_INLINE void WebPDataInit(WebPData* webp_data) {
if (webp_data != NULL) {
memset(webp_data, 0, sizeof(*webp_data));
}
}
*
* I tried just assigning the data direclty like "data.bytes = rawdata;"
* however that resulted in a "Invalid memory access" exception when
* calling "WebPAnimDecoderNewInternal".
*
* At the definition of the WebPData struct (see above) it says
* "'bytes' memory must be allocated using WebPMalloc() and such."
* so I assume that might be required to be used, but I'm not sure how.
*
* The way it is now throws a "Unsupported return type class [B in
* function WebPMalloc" exception.
*/
LibWebPDemux.WebPData data = new LibWebPDemux.WebPData();
data.bytes = LibWebP.INSTANCE.WebPMalloc(rawData.length);
data.length = rawData.length;
// This may not be required, instead "null" can be provided for default options
// LibWebPDemux.WebPAnimDecoderOptions options = new LibWebPDemux.WebPAnimDecoderOptions();
// int initResult = LibWebPDemux.INSTANCE.WebPAnimDecoderOptionsInitInternal(options, LibWebPDemux.WEBP_DEMUX_ABI_VERSION);
// System.out.println(initResult);
Pointer dec = LibWebPDemux.INSTANCE.WebPAnimDecoderNewInternal(data, null, LibWebPDemux.WEBP_DEMUX_ABI_VERSION);
System.out.println(dec);
// Just testing if accessing the lib works at all (it appears to)
// int version = LibWebPDemux.INSTANCE.WebPGetDemuxVersion();
// System.out.println(version);
return null;
}
//==========================
// libwebp
//==========================
public interface LibWebP extends Library {
LibWebP INSTANCE = Native.load("libwebp", LibWebP.class);
/*
[webp/decode.h]
// Retrieve basic header information: width, height.
// This function will also validate the header, returning true on success,
// false otherwise. '*width' and '*height' are only valid on successful return.
// Pointers 'width' and 'height' can be passed NULL if deemed irrelevant.
// Note: The following chunk sequences (before the raw VP8/VP8L data) are
// considered valid by this function:
// RIFF + VP8(L)
// RIFF + VP8X + (optional chunks) + VP8(L)
// ALPH + VP8 <-- Not a valid WebP format: only allowed for internal purpose.
// VP8(L) <-- Not a valid WebP format: only allowed for internal purpose.
WEBP_EXTERN int WebPGetInfo(const uint8_t* data, size_t data_size,
int* width, int* height);
*/
public int WebPGetInfo(byte[] data, int data_size, IntByReference width, IntByReference height);
/*
[webp/decode.h]
// Decodes WebP images pointed to by 'data' and returns RGBA samples, along
// with the dimensions in *width and *height. The ordering of samples in
// memory is R, G, B, A, R, G, B, A... in scan order (endian-independent).
// The returned pointer should be deleted calling WebPFree().
// Returns NULL in case of error.
WEBP_EXTERN uint8_t* WebPDecodeRGBA(const uint8_t* data, size_t data_size,
int* width, int* height);
*/
public Pointer WebPDecodeRGBA(byte[] data, int data_size, IntByReference width, IntByReference height);
/*
[webp/types.h]
// Allocates 'size' bytes of memory. Returns NULL upon error. Memory
// must be deallocated by calling WebPFree(). This function is made available
// by the core 'libwebp' library.
WEBP_EXTERN void* WebPMalloc(size_t size);
*/
public byte[] WebPMalloc(int size);
/*
[webp/types.h]
// Releases memory returned by the WebPDecode*() functions (from decode.h).
WEBP_EXTERN void WebPFree(void* ptr);
*/
public void WebPFree(Pointer pointer);
}
//==========================
// libwebpdemux
//==========================
public interface LibWebPDemux extends Library {
LibWebPDemux INSTANCE = Native.load("libwebpdemux", LibWebPDemux.class);
static final int WEBP_DEMUX_ABI_VERSION = 0x0107;
/*
[webp/demux.h]
// Internal, version-checked, entry point.
WEBP_EXTERN WebPAnimDecoder* WebPAnimDecoderNewInternal(
const WebPData*, const WebPAnimDecoderOptions*, int);
// Creates and initializes a WebPAnimDecoder object.
// Parameters:
// webp_data - (in) WebP bitstream. This should remain unchanged during the
// lifetime of the output WebPAnimDecoder object.
// dec_options - (in) decoding options. Can be passed NULL to choose
// reasonable defaults (in particular, color mode MODE_RGBA
// will be picked).
// Returns:
// A pointer to the newly created WebPAnimDecoder object, or NULL in case of
// parsing error, invalid option or memory error.
static WEBP_INLINE WebPAnimDecoder* WebPAnimDecoderNew(
const WebPData* webp_data, const WebPAnimDecoderOptions* dec_options) {
return WebPAnimDecoderNewInternal(webp_data, dec_options,
WEBP_DEMUX_ABI_VERSION);
}
*/
public Pointer WebPAnimDecoderNewInternal(WebPData webp_data, Structure dec_options, int version);
/*
[webp/mux_types.h]
// Data type used to describe 'raw' data, e.g., chunk data
// (ICC profile, metadata) and WebP compressed image data.
// 'bytes' memory must be allocated using WebPMalloc() and such.
struct WebPData {
const uint8_t* bytes;
size_t size;
};
*/
@Structure.FieldOrder({ "bytes", "length" })
public static class WebPData extends Structure {
public byte[] bytes;
public int length;
}
/*
[webp/demux.h]
// Returns the version number of the demux library, packed in hexadecimal using
// 8bits for each of major/minor/revision. E.g: v2.5.7 is 0x020507.
WEBP_EXTERN int WebPGetDemuxVersion(void);
*/
public int WebPGetDemuxVersion();
/*
[webp/demux.h]
// Internal, version-checked, entry point.
WEBP_EXTERN int WebPAnimDecoderOptionsInitInternal(
WebPAnimDecoderOptions*, int);
// Should always be called, to initialize a fresh WebPAnimDecoderOptions
// structure before modification. Returns false in case of version mismatch.
// WebPAnimDecoderOptionsInit() must have succeeded before using the
// 'dec_options' object.
static WEBP_INLINE int WebPAnimDecoderOptionsInit(
WebPAnimDecoderOptions* dec_options) {
return WebPAnimDecoderOptionsInitInternal(dec_options,
WEBP_DEMUX_ABI_VERSION);
}
*/
public int WebPAnimDecoderOptionsInitInternal(WebPAnimDecoderOptions options, int version);
/*
[webp/demux.h]
// Global options.
struct WebPAnimDecoderOptions {
// Output colorspace. Only the following modes are supported:
// MODE_RGBA, MODE_BGRA, MODE_rgbA and MODE_bgrA.
WEBP_CSP_MODE color_mode;
int use_threads; // If true, use multi-threaded decoding.
uint32_t padding[7]; // Padding for later use.
};
[webp/decode.h]
typedef enum WEBP_CSP_MODE {
MODE_RGB = 0, MODE_RGBA = 1,
MODE_BGR = 2, MODE_BGRA = 3,
MODE_ARGB = 4, MODE_RGBA_4444 = 5,
MODE_RGB_565 = 6,
// RGB-premultiplied transparent modes (alpha value is preserved)
MODE_rgbA = 7,
MODE_bgrA = 8,
MODE_Argb = 9,
MODE_rgbA_4444 = 10,
// YUV modes must come after RGB ones.
MODE_YUV = 11, MODE_YUVA = 12, // yuv 4:2:0
MODE_LAST = 13
} WEBP_CSP_MODE;
*/
@Structure.FieldOrder({ "color_mode", "use_threads", "padding" })
public static class WebPAnimDecoderOptions extends Structure {
public int color_mode; // enum
public int use_threads;
public int[] padding = new int[7];
}
/*
[webp/demux.h]
// Global information about the animation..
struct WebPAnimInfo {
uint32_t canvas_width;
uint32_t canvas_height;
uint32_t loop_count;
uint32_t bgcolor;
uint32_t frame_count;
uint32_t pad[4]; // padding for later use
};
*/
@Structure.FieldOrder({ "canvas_width", "canvas_height", "loop_count", "bgcolor", "frame_count", "pad" })
public static class WebPAnimInfo extends Structure {
public int canvas_width;
public int canvas_height;
public int loop_count;
public int bgcolor;
public int frame_count;
public int[] pad = new int[4];
}
}
//==========================
// Static image
//==========================
/**
* Decoding a static WebP image. I'm not sure if it's entirely correct, but
* at least an image comes out of it.
*/
private static ImageIcon decodeStatic(String url) throws Exception {
byte[] rawData = getBytesFromURL(new URL(url));
IntByReference widthRef = new IntByReference();
IntByReference heightRef = new IntByReference();
int result = LibWebP.INSTANCE.WebPGetInfo(rawData, rawData.length, widthRef, heightRef);
if (result == 1) {
// System.out.println(widthRef.getValue() + " " + heightRef.getValue());
Pointer pixelData = LibWebP.INSTANCE.WebPDecodeRGBA(rawData, rawData.length, widthRef, heightRef);
if (pixelData != null) {
int width = widthRef.getValue();
int height = heightRef.getValue();
int[] pixels = pixelData.getIntArray(0, width * height);
ColorModel colorModel;
colorModel = new DirectColorModel(32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);
SampleModel sampleModel = colorModel.createCompatibleSampleModel(width, height);
DataBufferInt db = new DataBufferInt(pixels, width * height);
WritableRaster raster = WritableRaster.createWritableRaster(sampleModel, db, null);
Image img = new BufferedImage(colorModel, raster, false, new Hashtable<Object, Object>());
return new ImageIcon(img);
}
}
System.out.println("Not a valid WebP");
return null;
}
//==========================
// General Helpers
//==========================
private static void showImages(ImageIcon... icons) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame();
frame.setLayout(new FlowLayout());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
for (ImageIcon icon : icons) {
frame.add(new JLabel(icon), BorderLayout.CENTER);
}
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
public static byte[] getBytesFromURL(URL url) throws Exception {
URLConnection c = url.openConnection();
try (InputStream input = c.getInputStream()) {
byte[] imageData = readAllBytes(input);
return imageData;
}
}
private static byte[] readAllBytes(InputStream input) throws IOException {
ByteArrayOutputStream result = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = input.read(buffer, 0, buffer.length)) != -1) {
result.write(buffer, 0, length);
}
return result.toByteArray();
}
}
1 ответ
Структурам JNA требуется, чтобы их информация о размере была правильно распределена, поэтому это сопоставление должно было дать вам ошибку без указания размера массива байтов:
@Structure.FieldOrder({ "bytes", "length" })
public static class WebPData extends Structure {
public byte[] bytes;
public int length;
}
Поскольку вы указали, что байты распределены в другом месте, указатель на массив должен быть просто сопоставлен с расширением . Также типlength
поэтому вам нужно использовать это сопоставление либо для *nix, либо дляSIZE_T
сопоставление для Windows.
@Structure.FieldOrder({ "bytes", "length" })
public static class WebPData extends Structure {
public Pointer bytes;
public BaseTSD.SIZE_T length;
}
Затем вы можете вручную выделить байты. Вы указываетеWebPMalloc()
делает это, но я не знаю, чем это отличается от простого использованияmalloc()
или эквивалент, который находится под капотом, если вы используете JNAnew Memory(size)
. Если вы используете эту функцию, вы должны передать значение длины в качестве параметра этой функции, и возвращаемое значение будетPointer
что вы бы установили какbytes
.
После того, как вы установили байты в указатель на выделение размера длины, вы можете передать структуру какwebp_data
аргумент.
Возможно, что с вашим отображениемbyte[]
собственный код пытался заставить его работать (поскольку это просто указатель), но с использованиемint
длина может быть неправильной, еслиsize_t
было 8 байт.