Skip to content

Commit

Permalink
Add metadata API.
Browse files Browse the repository at this point in the history
  • Loading branch information
fancycode committed Jan 30, 2025
1 parent 185caad commit 55b3482
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 16 deletions.
37 changes: 37 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,3 +236,40 @@ func (c *Context) GetImageHandle(id int) (*ImageHandle, error) {
runtime.SetFinalizer(&handle, freeHeifImageHandle)
return &handle, nil
}

func (c *Context) AddExifMetadata(handle *ImageHandle, data []byte) error {
runtime.KeepAlive(c)
runtime.KeepAlive(handle)

dataPtr := unsafe.Pointer(&data[0])
err := C.heif_context_add_exif_metadata(c.context, handle.handle, dataPtr, C.int(len(data)))
return convertHeifError(err)
}

func (c *Context) AddXmpMetadata(handle *ImageHandle, data []byte) error {
runtime.KeepAlive(c)
runtime.KeepAlive(handle)

dataPtr := unsafe.Pointer(&data[0])
err := C.heif_context_add_XMP_metadata(c.context, handle.handle, dataPtr, C.int(len(data)))
return convertHeifError(err)
}

func (c *Context) AddGenericMetadata(handle *ImageHandle, data []byte, item_type string, content_type string) error {
runtime.KeepAlive(c)
runtime.KeepAlive(handle)

dataPtr := unsafe.Pointer(&data[0])
var it *C.char
if item_type != "" {
it = C.CString(item_type)
defer C.free(unsafe.Pointer(it))
}
var ct *C.char
if content_type != "" {
ct = C.CString(content_type)
defer C.free(unsafe.Pointer(ct))
}
err := C.heif_context_add_generic_metadata(c.context, handle.handle, dataPtr, C.int(len(data)), it, ct)
return convertHeifError(err)
}
30 changes: 15 additions & 15 deletions encode_highlevel.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,73 +342,73 @@ func SetEncoderParameter(name string, value string) EncoderParameterSetter {
}

// EncodeFromImage is a high-level function to encode a Go Image to a new Context.
func EncodeFromImage(img image.Image, compression CompressionFormat, params ...EncoderParameterSetter) (*Context, error) {
func EncodeFromImage(img image.Image, compression CompressionFormat, params ...EncoderParameterSetter) (*Context, *ImageHandle, error) {
if err := checkLibraryVersion(); err != nil {
return nil, err
return nil, nil, err
}

var out *Image

switch i := img.(type) {
default:
return nil, fmt.Errorf("unsupported image type: %T", i)
return nil, nil, fmt.Errorf("unsupported image type: %T", i)
case *image.RGBA:
tmp, err := imageFromRGBA(i)
if err != nil {
return nil, fmt.Errorf("failed to create image: %v", err)
return nil, nil, fmt.Errorf("failed to create image: %v", err)
}
out = tmp
case *image.NRGBA:
tmp, err := imageFromNRGBA(i)
if err != nil {
return nil, fmt.Errorf("failed to create image: %v", err)
return nil, nil, fmt.Errorf("failed to create image: %v", err)
}
out = tmp
case *image.RGBA64:
tmp, err := imageFromRGBA64(i, compression)
if err != nil {
return nil, fmt.Errorf("failed to create image: %v", err)
return nil, nil, fmt.Errorf("failed to create image: %v", err)
}
out = tmp
case *image.NRGBA64:
tmp, err := imageFromNRGBA64(i, compression)
if err != nil {
return nil, fmt.Errorf("failed to create image: %v", err)
return nil, nil, fmt.Errorf("failed to create image: %v", err)
}
out = tmp
case *image.Gray:
tmp, err := imageFromGray(i)
if err != nil {
return nil, fmt.Errorf("failed to create image: %v", err)
return nil, nil, fmt.Errorf("failed to create image: %v", err)
}
out = tmp
case *image.YCbCr:
tmp, err := imageFromYCbCr(i)
if err != nil {
return nil, fmt.Errorf("failed to create image: %v", err)
return nil, nil, fmt.Errorf("failed to create image: %v", err)
}
out = tmp
}

ctx, err := NewContext()
if err != nil {
return nil, fmt.Errorf("failed to create HEIF context: %v", err)
return nil, nil, fmt.Errorf("failed to create HEIF context: %v", err)
}

enc, err := ctx.NewEncoder(compression)
if err != nil {
return nil, fmt.Errorf("failed to create encoder: %v", err)
return nil, nil, fmt.Errorf("failed to create encoder: %v", err)
}

for _, param := range params {
if err := param(enc); err != nil {
return nil, fmt.Errorf("error setting parameter: %w", err)
return nil, nil, fmt.Errorf("error setting parameter: %w", err)
}
}

encOpts, err := NewEncodingOptions()
if err != nil {
return nil, fmt.Errorf("failed to get encoding options: %v", err)
return nil, nil, fmt.Errorf("failed to get encoding options: %v", err)
}

defer runtime.KeepAlive(ctx)
Expand All @@ -419,9 +419,9 @@ func EncodeFromImage(img image.Image, compression CompressionFormat, params ...E
var handle ImageHandle
err2 := C.heif_context_encode_image(ctx.context, out.image, enc.encoder, encOpts.options, &handle.handle)
if err := convertHeifError(err2); err != nil {
return nil, fmt.Errorf("failed to encode image: %v", err)
return nil, nil, fmt.Errorf("failed to encode image: %v", err)
}

runtime.SetFinalizer(&handle, freeHeifImageHandle)
return ctx, nil
return ctx, &handle, nil
}
2 changes: 1 addition & 1 deletion encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func TestEncoder(t *testing.T) {
require.True(HaveEncoderForFormat(codec))

img := loadImage(t, "testdata/example-1.jpg")
ctx, err := EncodeFromImage(img, codec,
ctx, _, err := EncodeFromImage(img, codec,
SetEncoderQuality(75),
SetEncoderLossless(LosslessModeDisabled),
SetEncoderLoggingLevel(LoggingLevelFull),
Expand Down
3 changes: 3 additions & 0 deletions heif_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ func CheckHeifImage(t *testing.T, handle *ImageHandle, thumbnail bool) {
})
}
}

meta_ids := handle.GetMetadataBlockIDs("")
assert.Empty(meta_ids)
})

decodeTests := []decodeTest{
Expand Down
48 changes: 48 additions & 0 deletions image_handle.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import "C"

import (
"runtime"
"unsafe"
)

// ImageHandle contains information about an image in a libheif Context.
Expand Down Expand Up @@ -159,3 +160,50 @@ func (h *ImageHandle) DecodeImage(colorspace Colorspace, chroma Chroma, options
runtime.SetFinalizer(&image, freeHeifImage)
return &image, nil
}

func (h *ImageHandle) GetMetadataBlockIDs(filter string) []int {
defer runtime.KeepAlive(h)

var f *C.char
if filter != "" {
f = C.CString(filter)
defer C.free(unsafe.Pointer(f))
}
num := int(C.heif_image_handle_get_number_of_metadata_blocks(h.handle, f))
if num == 0 {
return nil
}

ids := make([]C.heif_item_id, num)
C.heif_image_handle_get_list_of_metadata_block_IDs(h.handle, f, &ids[0], C.int(num))
return convertItemIDs(ids, num)
}

func (h *ImageHandle) GetMetadataContentType(block_id int) string {
defer runtime.KeepAlive(h)

ct := C.heif_image_handle_get_metadata_content_type(h.handle, C.heif_item_id(block_id))
if ct == nil {
return ""
}

return C.GoString(ct)
}

func (h *ImageHandle) GetMetadata(block_id int) ([]byte, error) {
defer runtime.KeepAlive(h)

var err C.struct_heif_error
var result []byte
if size := C.heif_image_handle_get_metadata_size(h.handle, C.heif_item_id(block_id)); size > 0 {
result = make([]byte, size)
err = C.heif_image_handle_get_metadata(h.handle, C.heif_item_id(block_id), unsafe.Pointer(&result[0]))
} else {
err = C.heif_image_handle_get_metadata(h.handle, C.heif_item_id(block_id), nil)
}
if err := convertHeifError(err); err != nil {
return nil, err
}

return result, nil
}

0 comments on commit 55b3482

Please sign in to comment.