使用libwebp dll和JNA解码动画图像
我需要在Java中阅读静态和动画的WebP图像。 libwepb ,我已经下载了 source> source 并在本机工具命令提示在这样的窗口上为vs 2022 vs 2022:
..\libwebp-1.2.2>nmake /f Makefile.vc CFG=release-dynamic RTLIBCFG=dynamic OBJDIR=output
这导致了文件 libwebp.dll
, libwebpdecoder.dll
和 libwebpdemux.dll
。我正在为JNA使用 JNA-5.12.1.JAR
。我能够使用 libwebp.dll
和函数 webpdecodergba
来解码静态图像,但是解码动画映像不适用。据我了解, libwebpdemux.dll
中的demux函数是必需的。
我试图实现
WebPAnimDecoder* dec = WebPAnimDecoderNew(webp_data, &dec_options);
来自 documentation 它看起来像 dec_options
可能只是默认值为默认值,但我不确定它想要 webp_data
。我试图实现,但我不确定这是否正确。
@Structure.FieldOrder({ "bytes", "length" })
public static class WebPData extends Structure {
public byte[] bytes;
public int length;
}
我只需直接分配字节> data.bytes = rowdata; 即可获得“无效的内存访问”异常。在WebP struct定义上,它说“必须使用 webpmalloc()等等。不知何故,但是我不知道这将如何工作,如何将数据获取?现在,它的方式抛出了一个异常,抱怨返回类型。
令我感到困惑的是,调用
webpanimdecodernew
,但是该功能没有导出,因此我无法访问它。相反,有一个称为 带有其他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();
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
JNA结构需要适当分配其尺寸信息,因此该映射应该给您一个错误,而不指定字节数组大小:
由于您已指示字节是在其他地方分配的,因此,阵列的指针应仅以<<<代码>指针。另外,
长度的类型
是size_t
,因此您需要将该映射用于 *nix或Windows的size_t
映射。然后,您可以手动分配字节。您指出
webpmalloc()
这样做,但我不知道这与仅使用malloc()
或等效的情况有何不同,如果您使用JNA的新内存(大小)。如果使用该函数,则将长度值作为参数传递给该函数,返回值将是
指针
,您将将其设置为bytes
。将字节设置为指向长度大小分配的指针后,您可以将结构传递为
webp_data
参数。在您的映射
byte []
时,本机代码试图使其起作用(因为这只是指针),但是使用int
为长度可能没有如果size_t
为8个字节,请更正。JNA structures need their size information to be properly allocated, so this mapping should have given you an error without specifying the byte array size:
Since you've indicated the bytes are allocated elsewhere, the pointer to the array should just be mapped with a
Pointer
. Also the type oflength
issize_t
so you need to use that mapping for either *nix, or theSIZE_T
mapping for Windows.You could then manually allocate the bytes. You indicate
WebPMalloc()
does that but I don't know how that's any different than just usingmalloc()
or equivalent which is under the hood if you use JNA'snew Memory(size)
. If you use that function you would pass the length value as a parameter to that function and the return value would be aPointer
that you would set asbytes
.Once you've set the bytes to a pointer to the length-sized allocation, you can pass the structure as the
webp_data
argument.It is possible that with your mapping of
byte[]
the native code tried to make it work (as that's just a pointer) but usingint
for the length may have not been correct ifsize_t
was 8 bytes.感谢 daniel widdis的答案我更改了
webpdata
'sbytes
再次将指针视为指针。我之前并不真正知道如何将我的原始数据获取到其中,不确定为什么我之前没有注意到指针的write()
方法。webpdata
's长度
字段的类型几乎没关系(简短
,int
,<代码>长,float
,double
都可以使用),我想它会自动将其转换为以某种方式。不确定这是否会在不同的情况/系统中破裂。new Memory()
和本机webpmalloc()
函数似乎对分配内存起作用。我已经查看了该功能的实际功能:我不完全理解它的作用,但似乎正在进行一些其他检查和管理。不确定是否有必要使用它,但如果图书馆推荐它,也可能会使用它吗?
将数据作为指针提供后,我得到了解码器对象。我唯一遇到的问题的是获得下一个帧的功能:
这似乎是双重指针吗?因此,我必须从参考文献中获得指针,然后再从中获得指针:
这是更新的代码,对实际使用的内容进行了一些清理。我不确定它是否完全正确,但是我现在得到了框架和其他信息,因此至少应该不是那么错误。
它肯定需要进行一些正确的错误检查,我不确定之后是否需要清理任何记忆。
Thanks to Daniel Widdis' answer I changed the
WebPData
'sbytes
field to a Pointer again. I didn't really know before how to get my raw data into that, not sure why I didn't notice the Pointer'swrite()
method before.The type of the
WebPData
'slength
field almost doesn't seem to matter (short
,int
,long
,float
,double
all worked), I guess it automatically converts it somehow. Not sure if that could break under different circumstances/systems though.Both
new Memory()
and the nativeWebPMalloc()
function seem to work for allocating the memory. I've looked at what the function actually does:I don't fully understand what that does, but it seems to be doing some additional checks and management. Not sure if it's necessary to use it, but might as well if the library recommends it?
After providing the data as a Pointer, I got the decoder object. The only thing from there that I had some issues with was the function to get the next frame:
It seems to be a double pointer? So I had to get the Pointer from the reference, and then the Pointer from that again:
Here's the updated code, cleaned up a bit to what is actually used. I'm not sure if it's entirely correct, but I'm getting the frames and other info now, so it shouldn't be that wrong at least.
It definitely needs some proper error checking and I'm not sure if I need to clean up any more memory afterwards.