Skip to content

Instantly share code, notes, and snippets.

@muffinpowered
Last active January 18, 2025 19:21
Show Gist options
  • Select an option

  • Save muffinpowered/c45d1f4537879b504be38f4c6bb91cc5 to your computer and use it in GitHub Desktop.

Select an option

Save muffinpowered/c45d1f4537879b504be38f4c6bb91cc5 to your computer and use it in GitHub Desktop.
golang photo2avif (libavif, cgo)
package main
import (
"bytes"
"image"
_ "image/png"
_ "image/jpeg"
"fmt"
"os"
"unsafe"
)
/*
#cgo LDFLAGS: -lavif
#include <avif/avif.h>
#include <stdio.h>
#include <string.h>
uint8_t* encode(
uint8_t *rgb_in,
int width, int height,
int depth, int chroma,
int maxThreads,
int quality, int speed,
size_t *size
) {
avifResult result;
avifImage *image = avifImageCreate(width, height, depth, chroma);
if (!image) {
fprintf(stderr, "[avifImageCreate] Out of memory\n");
goto cleanup;
}
avifRGBImage rgb;
avifRGBImageSetDefaults(&rgb, image);
rgb.maxThreads = maxThreads;
rgb.alphaPremultiplied = 1;
result = avifRGBImageAllocatePixels(&rgb);
if (result != AVIF_RESULT_OK) {
fprintf(stderr, "[avifRGBImageAllocatePixels] Out of memory\n");
goto cleanup;
}
rgb.pixels = rgb_in;
result = avifImageRGBToYUV(image, &rgb);
if (result != AVIF_RESULT_OK) {
fprintf(stderr, "[avifImageRGBToYUV] Out of memory\n");
goto cleanup;
}
avifRWData output = AVIF_DATA_EMPTY;
avifEncoder *encoder = avifEncoderCreate();
if (!encoder) {
fprintf(stderr, "[avifEncoderCreate] Out of memory\n");
goto cleanup;
}
encoder->maxThreads = maxThreads;
encoder->quality = quality;
encoder->speed = speed;
result = avifEncoderAddImage(encoder, image, 1, AVIF_ADD_IMAGE_FLAG_SINGLE);
if (result != AVIF_RESULT_OK) {
fprintf(stderr, "Failed to add image to encoder: %s\n", avifResultToString(result));
goto cleanup;
}
result = avifEncoderFinish(encoder, &output);
if (result != AVIF_RESULT_OK) {
fprintf(stderr, "Failed to finish encode: %s\n", avifResultToString(result));
goto cleanup;
}
*size = output.size;
return output.data;
cleanup:
if (image) {
avifImageDestroy(image);
}
if (encoder) {
avifEncoderDestroy(encoder);
}
avifRWDataFree(&output);
avifRGBImageFreePixels(&rgb);
}
*/
import "C"
func Encode(img image.Image, depth, maxThreads, quality, speed int) []byte {
bounds := img.Bounds()
width := bounds.Dx()
height := bounds.Dy()
pixels := make([]byte, width*height*4)
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
r, g, b, a := img.At(x, y).RGBA()
i := (y*width + x) * 4
pixels[i+0] = byte(r >> 8)
pixels[i+1] = byte(g >> 8)
pixels[i+2] = byte(b >> 8)
pixels[i+3] = byte(a >> 8)
}
}
var size C.size_t
data := C.encode((*C.uint8_t)(unsafe.Pointer(&pixels[0])),
C.int(width), C.int(height),
C.int(depth), C.int(C.AVIF_PIXEL_FORMAT_YUV444),
C.int(maxThreads),
C.int(quality), C.int(speed),
&size)
return C.GoBytes(unsafe.Pointer(data), C.int(size))
}
func main() {
DecodeNcodePhoto("./src/beads.png", "./dst/beads.avif")
DecodeNcodePhoto("./src/icy-and-rocky-worlds.jpg", "./dst/icy-and-rocky-worlds.avif")
fmt.Println("Files saved successfully!")
}
func DecodeNcodePhoto(srcPath, dstPath string) {
src, err := os.ReadFile(srcPath)
if err != nil {
panic(err)
}
img, _, err := image.Decode(bytes.NewReader(src))
if err != nil {
panic(err)
}
dst, err := os.Create(dstPath)
if err != nil {
panic(err)
}
defer dst.Close()
encodedBytes := Encode(img, 8, 6, 75, 10)
if _, err := dst.Write(encodedBytes); err != nil {
panic(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment