Декодирование анимированного изображения с использованием библиотек 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 байт.

Другие вопросы по тегам