diff --git a/.gitignore b/.gitignore index d668d01..5f343c6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /output.webp .idea .DS_Store +examples/ +testdata/ diff --git a/capi.go b/capi.go index d36162c..b2cd790 100644 --- a/capi.go +++ b/capi.go @@ -24,12 +24,140 @@ package webp #include "webp.h" #include +#include +#include +#include +#include +#include #include +#include +#include + +// 检查是否为动画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" ) @@ -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 +} diff --git a/webp.go b/webp.go index 1205695..9dc8df1 100644 --- a/webp.go +++ b/webp.go @@ -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) +}