Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/output.webp
.idea
.DS_Store
examples/
testdata/
243 changes: 243 additions & 0 deletions capi.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,140 @@ package webp
#include "webp.h"

#include <webp/decode.h>
#include <webp/encode.h>
#include <webp/mux.h>
#include <webp/demux.h>
#include <webp/mux_types.h>
#include <webp/types.h>

#include <stdlib.h>
#include <string.h>
#include <stdint.h>

// 检查是否为动画WebP
int webpIsAnimated(const uint8_t* data, size_t data_size) {
WebPData webp_data = {data, data_size};
WebPDemuxer* demux = WebPDemux(&webp_data);
if (!demux) return 0;

uint32_t frame_count = WebPDemuxGetI(demux, WEBP_FF_FRAME_COUNT);
int is_animated = frame_count > 1;

WebPDemuxDelete(demux);
return is_animated;
}

// 获取动画信息
int webpGetAnimInfo(const uint8_t* data, size_t data_size,
int* canvas_width, int* canvas_height,
int* frame_count, int* loop_count) {
WebPData webp_data = {data, data_size};
WebPDemuxer* demux = WebPDemux(&webp_data);
if (!demux) return 0;

*canvas_width = WebPDemuxGetI(demux, WEBP_FF_CANVAS_WIDTH);
*canvas_height = WebPDemuxGetI(demux, WEBP_FF_CANVAS_HEIGHT);
*frame_count = WebPDemuxGetI(demux, WEBP_FF_FRAME_COUNT);
*loop_count = WebPDemuxGetI(demux, WEBP_FF_LOOP_COUNT);

WebPDemuxDelete(demux);
return 1;
}

// 解码动画WebP的第一帧
uint8_t* webpDecodeAnimFirstFrame(const uint8_t* data, size_t data_size,
int* width, int* height) {
WebPData webp_data = {data, data_size};
WebPDemuxer* demux = WebPDemux(&webp_data);
if (!demux) return NULL;

WebPIterator iter;
if (!WebPDemuxGetFrame(demux, 1, &iter)) {
WebPDemuxDelete(demux);
return NULL;
}

// 解码第一帧
uint8_t* rgba = WebPDecodeRGBA(iter.fragment.bytes, iter.fragment.size, width, height);

WebPDemuxReleaseIterator(&iter);
WebPDemuxDelete(demux);

return rgba;
}

// 解码动画WebP的所有帧
int webpDecodeAnimFrames(const uint8_t* data, size_t data_size,
uint8_t*** frames, int** timestamps, int** durations,
int* frame_count, int* width, int* height) {
WebPData webp_data = {data, data_size};
WebPDemuxer* demux = WebPDemux(&webp_data);
if (!demux) return 0;

*frame_count = WebPDemuxGetI(demux, WEBP_FF_FRAME_COUNT);
*width = WebPDemuxGetI(demux, WEBP_FF_CANVAS_WIDTH);
*height = WebPDemuxGetI(demux, WEBP_FF_CANVAS_HEIGHT);

if (*frame_count <= 0) {
WebPDemuxDelete(demux);
return 0;
}

// 分配内存
*frames = (uint8_t**)malloc(*frame_count * sizeof(uint8_t*));
*timestamps = (int*)malloc(*frame_count * sizeof(int));
*durations = (int*)malloc(*frame_count * sizeof(int));

if (!*frames || !*timestamps || !*durations) {
if (*frames) free(*frames);
if (*timestamps) free(*timestamps);
if (*durations) free(*durations);
WebPDemuxDelete(demux);
return 0;
}

WebPIterator iter;
int frame_idx = 0;

if (WebPDemuxGetFrame(demux, 1, &iter)) {
do {
if (frame_idx >= *frame_count) break;

int frame_width, frame_height;
uint8_t* rgba = WebPDecodeRGBA(iter.fragment.bytes, iter.fragment.size,
&frame_width, &frame_height);

if (rgba) {
(*frames)[frame_idx] = rgba;
(*timestamps)[frame_idx] = frame_idx * 100; // 简单的时间戳计算
(*durations)[frame_idx] = iter.duration;
frame_idx++;
}
} while (WebPDemuxNextFrame(&iter));

WebPDemuxReleaseIterator(&iter);
}

WebPDemuxDelete(demux);
return frame_idx; // 返回实际解码的帧数
}

// 释放帧数据
void webpFreeFrames(uint8_t** frames, int* timestamps, int* durations, int frame_count) {
if (frames) {
for (int i = 0; i < frame_count; i++) {
if (frames[i]) free(frames[i]);
}
free(frames);
}
if (timestamps) free(timestamps);
if (durations) free(durations);
}
*/
import "C"
import (
"errors"
"image"
"unsafe"
)

Expand Down Expand Up @@ -537,3 +665,118 @@ func webpDelXMP(data []byte) (newData []byte, err error) {
copy(newData, ((*[1 << 30]byte)(unsafe.Pointer(cptr)))[0:len(newData):len(newData)])
return
}

// 动画WebP相关函数

func webpIsAnimated(data []byte) bool {
if len(data) == 0 {
return false
}

result := C.webpIsAnimated((*C.uint8_t)(unsafe.Pointer(&data[0])), C.size_t(len(data)))
return result != 0
}

func webpGetAnimInfo(data []byte) (*AnimInfo, error) {
if len(data) == 0 {
return nil, errors.New("webpGetAnimInfo: data is empty")
}

var cCanvasWidth, cCanvasHeight, cFrameCount, cLoopCount C.int
result := C.webpGetAnimInfo(
(*C.uint8_t)(unsafe.Pointer(&data[0])), C.size_t(len(data)),
&cCanvasWidth, &cCanvasHeight, &cFrameCount, &cLoopCount,
)

if result == 0 {
return nil, errors.New("webpGetAnimInfo: failed to get animation info")
}

return &AnimInfo{
CanvasWidth: int(cCanvasWidth),
CanvasHeight: int(cCanvasHeight),
FrameCount: int(cFrameCount),
LoopCount: int(cLoopCount),
}, nil
}

func webpDecodeAnimFirstFrame(data []byte) (*image.RGBA, error) {
if len(data) == 0 {
return nil, errors.New("webpDecodeAnimFirstFrame: data is empty")
}

var cWidth, cHeight C.int
cptr := C.webpDecodeAnimFirstFrame(
(*C.uint8_t)(unsafe.Pointer(&data[0])), C.size_t(len(data)),
&cWidth, &cHeight,
)

if cptr == nil {
return nil, errors.New("webpDecodeAnimFirstFrame: failed to decode first frame")
}
defer C.free(unsafe.Pointer(cptr))

width := int(cWidth)
height := int(cHeight)
pix := make([]byte, width*height*4)
copy(pix, ((*[1 << 30]byte)(unsafe.Pointer(cptr)))[0:len(pix):len(pix)])

return &image.RGBA{
Pix: pix,
Stride: 4 * width,
Rect: image.Rect(0, 0, width, height),
}, nil
}

func webpDecodeAnimFrames(data []byte) ([]*Frame, error) {
if len(data) == 0 {
return nil, errors.New("webpDecodeAnimFrames: data is empty")
}

var cFrames **C.uint8_t
var cTimestamps, cDurations *C.int
var cFrameCount, cWidth, cHeight C.int

result := C.webpDecodeAnimFrames(
(*C.uint8_t)(unsafe.Pointer(&data[0])), C.size_t(len(data)),
&cFrames, &cTimestamps, &cDurations,
&cFrameCount, &cWidth, &cHeight,
)

if result == 0 {
return nil, errors.New("webpDecodeAnimFrames: failed to decode frames")
}

defer C.webpFreeFrames(cFrames, cTimestamps, cDurations, cFrameCount)

frameCount := int(cFrameCount)
width := int(cWidth)
height := int(cHeight)
pixelCount := width * height * 4

// 转换C数组到Go切片
frames := make([]*Frame, frameCount)

cFramesSlice := (*[1 << 20]*C.uint8_t)(unsafe.Pointer(cFrames))[:frameCount:frameCount]
cTimestampsSlice := (*[1 << 20]C.int)(unsafe.Pointer(cTimestamps))[:frameCount:frameCount]
cDurationsSlice := (*[1 << 20]C.int)(unsafe.Pointer(cDurations))[:frameCount:frameCount]

for i := 0; i < frameCount; i++ {
if cFramesSlice[i] != nil {
pix := make([]byte, pixelCount)
copy(pix, ((*[1 << 30]byte)(unsafe.Pointer(cFramesSlice[i])))[0:pixelCount:pixelCount])

frames[i] = &Frame{
Image: &image.RGBA{
Pix: pix,
Stride: 4 * width,
Rect: image.Rect(0, 0, width, height),
},
Timestamp: int(cTimestampsSlice[i]),
Duration: int(cDurationsSlice[i]),
}
}
}

return frames, nil
}
47 changes: 47 additions & 0 deletions webp.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,50 @@ func GetMetadata(data []byte, format string) (metadata []byte, err error) {
func SetMetadata(data, metadata []byte, format string) (newData []byte, err error) {
return webpSetMetadata(data, metadata, format)
}

// AnimInfo contains animation information
type AnimInfo struct {
CanvasWidth int
CanvasHeight int
FrameCount int
LoopCount int
}

// Frame represents a single frame in an animation
type Frame struct {
Image *image.RGBA
Timestamp int
Duration int
}

// IsAnimated checks if the WebP data contains an animation
func IsAnimated(data []byte) bool {
return webpIsAnimated(data)
}

// GetAnimInfo returns animation information
func GetAnimInfo(data []byte) (*AnimInfo, error) {
return webpGetAnimInfo(data)
}

// DecodeAnimFirstFrame decodes the first frame of an animated WebP
func DecodeAnimFirstFrame(data []byte) (*image.RGBA, error) {
return webpDecodeAnimFirstFrame(data)
}

// DecodeAnimFrames decodes all frames of an animated WebP
func DecodeAnimFrames(data []byte) ([]*Frame, error) {
return webpDecodeAnimFrames(data)
}

// ConvertAnimToStatic converts an animated WebP to a static WebP (first frame)
func ConvertAnimToStatic(data []byte) ([]byte, error) {
// 解码第一帧
firstFrame, err := DecodeAnimFirstFrame(data)
if err != nil {
return nil, err
}

// 编码为静态 WebP
return EncodeRGBA(firstFrame, 80.0)
}