go/heif/heif.go (1,165 lines of code) (raw):

/* * GO interface to libheif * Copyright (c) 2018 Dirk Farin <dirk.farin@gmail.com> * * This file is part of heif, an example application using libheif. * * libheif is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * libheif is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with libheif. If not, see <http://www.gnu.org/licenses/>. */ package heif /* #cgo pkg-config: libheif #include <stdlib.h> #include <string.h> // We use 'memcpy' #include <libheif/heif.h> */ import "C" import ( "fmt" "image" "image/color" "io" "io/ioutil" "runtime" "unsafe" ) func GetVersion() string { return C.GoString(C.heif_get_version()) } type Compression C.enum_heif_compression_format const ( CompressionUndefined = C.heif_compression_undefined CompressionHEVC = C.heif_compression_HEVC CompressionAV1 = C.heif_compression_AV1 CompressionAVC = C.heif_compression_AVC CompressionJPEG = C.heif_compression_JPEG CompressionJPEG2000 = C.heif_compression_JPEG2000 CompressionVVC = C.heif_compression_VVC CompressionEVC = C.heif_compression_EVC CompressionUncompressed = C.heif_compression_uncompressed CompressionMask = C.heif_compression_mask CompressionHTJ2K = C.heif_compression_HTJ2K ) type Chroma C.enum_heif_chroma const ( ChromaUndefined = C.heif_chroma_undefined ChromaMonochrome = C.heif_chroma_monochrome Chroma420 = C.heif_chroma_420 Chroma422 = C.heif_chroma_422 Chroma444 = C.heif_chroma_444 ChromaInterleavedRGB = C.heif_chroma_interleaved_RGB ChromaInterleavedRGBA = C.heif_chroma_interleaved_RGBA ChromaInterleavedRRGGBBAA_BE = C.heif_chroma_interleaved_RRGGBBAA_BE ChromaInterleavedRRGGBBAA_LE = C.heif_chroma_interleaved_RRGGBBAA_LE ChromaInterleavedRRGGBB_BE = C.heif_chroma_interleaved_RRGGBB_BE ChromaInterleavedRRGGBB_LE = C.heif_chroma_interleaved_RRGGBB_LE ChromaInterleaved24Bit = C.heif_chroma_interleaved_24bit ChromaInterleaved32Bit = C.heif_chroma_interleaved_32bit ) type ChromaDownsamplingAlgorithm C.enum_heif_chroma_downsampling_algorithm const ( ChromaDownsamplingAverage = C.heif_chroma_downsampling_average ChromaDownsamplingNearestNeighbor = C.heif_chroma_downsampling_nearest_neighbor ChromaDownsamplingSharpYUV = C.heif_chroma_downsampling_sharp_yuv ) type ChromaUpsamplingAlgorithm C.enum_heif_chroma_upsampling_algorithm const ( ChromaUpsamplingNearestNeighbor = C.heif_chroma_upsampling_nearest_neighbor ChromaUpsamplingBilinear = C.heif_chroma_upsampling_bilinear ) type Colorspace C.enum_heif_colorspace const ( ColorspaceUndefined = C.heif_colorspace_undefined ColorspaceYCbCr = C.heif_colorspace_YCbCr ColorspaceRGB = C.heif_colorspace_RGB ColorspaceMonochrome = C.heif_colorspace_monochrome ) type Channel C.enum_heif_channel const ( ChannelY = C.heif_channel_Y ChannelCb = C.heif_channel_Cb ChannelCr = C.heif_channel_Cr ChannelR = C.heif_channel_R ChannelG = C.heif_channel_G ChannelB = C.heif_channel_B ChannelAlpha = C.heif_channel_Alpha ChannelInterleaved = C.heif_channel_interleaved ) type ProgressStep C.enum_heif_progress_step const ( ProgressStepTotal = C.heif_progress_step_total ProgressStepLoadTile = C.heif_progress_step_load_tile ) type LosslessMode int const ( LosslessModeDisabled LosslessMode = iota LosslessModeEnabled ) type LoggingLevel int const ( LoggingLevelNone LoggingLevel = iota LoggingLevelBasic LoggingLevelAdvanced LoggingLevelFull ) // --- HeifError type ErrorCode C.enum_heif_error_code const ( ErrorOK = C.heif_error_Ok // Input file does not exist. ErrorInputDoesNotExist = C.heif_error_Input_does_not_exist // Error in input file. Corrupted or invalid content. errorInvalidInput = C.heif_error_Invalid_input // Input file type is not supported. ErrorUnsupportedFiletype = C.heif_error_Unsupported_filetype // Image requires an unsupported decoder feature. ErrorUnsupportedFeature = C.heif_error_Unsupported_feature // Library API has been used in an invalid way. ErrorUsage = C.heif_error_Usage_error // Could not allocate enough memory. ErrorMemoryAllocation = C.heif_error_Memory_allocation_error // The decoder plugin generated an error ErrorDecoderPlugin = C.heif_error_Decoder_plugin_error // The decoder plugin generated an error ErrorEncoderPlugin = C.heif_error_Encoder_plugin_error // Error during encoding or when writing to the output ErrorEncoding = C.heif_error_Encoding_error // Application has asked for a color profile type that does not exist ErrorColorProfileDoesNotExist = C.heif_error_Color_profile_does_not_exist ErrorPluginLoadingError = C.heif_error_Plugin_loading_error ) type ErrorSubcode C.enum_heif_suberror_code const ( // no further information available SuberrorUnspecified = C.heif_suberror_Unspecified // --- Invalid_input --- // End of data reached unexpectedly. SuberrorEndOfData = C.heif_suberror_End_of_data // Size of box (defined in header) is wrong SuberrorInvalidBoxSize = C.heif_suberror_Invalid_box_size // Mandatory 'ftyp' box is missing SuberrorNoFtypBox = C.heif_suberror_No_ftyp_box SuberrorNoIdatBox = C.heif_suberror_No_idat_box SuberrorNoMetaBox = C.heif_suberror_No_meta_box SuberrorNoHdlrBox = C.heif_suberror_No_hdlr_box SuberrorNoHvcCBox = C.heif_suberror_No_hvcC_box SuberrorNoPitmBox = C.heif_suberror_No_pitm_box SuberrorNoIpcoBox = C.heif_suberror_No_ipco_box SuberrorNoIpmaBox = C.heif_suberror_No_ipma_box SuberrorNoIlocBox = C.heif_suberror_No_iloc_box SuberrorNoIinfBox = C.heif_suberror_No_iinf_box SuberrorNoIprpBox = C.heif_suberror_No_iprp_box SuberrorNoIrefBox = C.heif_suberror_No_iref_box SuberrorNoPictHandler = C.heif_suberror_No_pict_handler // An item property referenced in the 'ipma' box is not existing in the 'ipco' container. SuberrorIpmaBoxReferencesNonexistingProperty = C.heif_suberror_Ipma_box_references_nonexisting_property // No properties have been assigned to an item. SuberrorNoPropertiesAssignedToItem = C.heif_suberror_No_properties_assigned_to_item // Image has no (compressed) data SuberrorNoItemData = C.heif_suberror_No_item_data // Invalid specification of image grid (tiled image) SuberrorInvalidGridData = C.heif_suberror_Invalid_grid_data // Tile-images in a grid image are missing SuberrorMissingGridImages = C.heif_suberror_Missing_grid_images SuberrorNoAV1CBox = C.heif_suberror_No_av1C_box SuberrorInvalidCleanAperture = C.heif_suberror_Invalid_clean_aperture // Invalid specification of overlay image SuberrorInvalidOverlayData = C.heif_suberror_Invalid_overlay_data // Overlay image completely outside of visible canvas area SuberrorOverlayImageOutsideOfCanvas = C.heif_suberror_Overlay_image_outside_of_canvas SuberrorPluginIsNotLoaded = C.heif_suberror_Plugin_is_not_loaded SuberrorPluginLoadingError = C.heif_suberror_Plugin_loading_error SuberrorAuxiliaryImageTypeUnspecified = C.heif_suberror_Auxiliary_image_type_unspecified SuberrorCannotReadPluginDirectory = C.heif_suberror_Cannot_read_plugin_directory SuberrorNoMatchingDecoderInstalled = C.heif_suberror_No_matching_decoder_installed SuberrorNoOrInvalidPrimaryItem = C.heif_suberror_No_or_invalid_primary_item SuberrorNoInfeBox = C.heif_suberror_No_infe_box SuberrorUnknownColorProfileType = C.heif_suberror_Unknown_color_profile_type SuberrorWrongTileImageChromaFormat = C.heif_suberror_Wrong_tile_image_chroma_format SuberrorInvalidFractionalNumber = C.heif_suberror_Invalid_fractional_number SuberrorInvalidImageSize = C.heif_suberror_Invalid_image_size SuberrorCameraIntrinsicMatrixUndefined = C.heif_suberror_Camera_intrinsic_matrix_undefined SuberrorCameraExtrinsicMatrixUndefined = C.heif_suberror_Camera_extrinsic_matrix_undefined SuberrorDecompressionInvalidData = C.heif_suberror_Decompression_invalid_data // --- Memory_allocation_error --- // A security limit preventing unreasonable memory allocations was exceeded by the input file. // Please check whether the file is valid. If it is, contact us so that we could increase the // security limits further. SuberrorSecurityLimitExceeded = C.heif_suberror_Security_limit_exceeded CompressionInitialisationError = C.heif_suberror_Compression_initialisation_error // --- Usage_error --- // An item ID was used that is not present in the file. SuberrorNonexistingItemReferenced = C.heif_suberror_Nonexisting_item_referenced // also used for Invalid_input // An API argument was given a NULL pointer, which is not allowed for that function. SuberrorNullPointerArgument = C.heif_suberror_Null_pointer_argument // Image channel referenced that does not exist in the image SuberrorNonexistingImageChannelReferenced = C.heif_suberror_Nonexisting_image_channel_referenced // The version of the passed plugin is not supported. SuberrorUnsupportedPluginVersion = C.heif_suberror_Unsupported_plugin_version // The version of the passed writer is not supported. SuberrorUnsupportedWriterVersion = C.heif_suberror_Unsupported_writer_version // The given (encoder) parameter name does not exist. SuberrorUnsupportedParameter = C.heif_suberror_Unsupported_parameter // The value for the given parameter is not in the valid range. SuberrorInvalidParameterValue = C.heif_suberror_Invalid_parameter_value SuberrorInvalidProperty = C.heif_suberror_Invalid_property SuberrorItemReferenceCycle = C.heif_suberror_Item_reference_cycle SuberrorInvalidPixiBox = C.heif_suberror_Invalid_pixi_box SuberrorInvalidRegionData = C.heif_suberror_Invalid_region_data SuberrorNoIspeProperty = C.heif_suberror_No_ispe_property SuberrorWrongTileImagePixelDepth = C.heif_suberror_Wrong_tile_image_pixel_depth SuberrorUnknownNCLXColorPrimaries = C.heif_suberror_Unknown_NCLX_color_primaries SuberrorUnknownNCLXTransferCharacteristics = C.heif_suberror_Unknown_NCLX_transfer_characteristics SuberrorUnknownNCLXMatrixCoefficients = C.heif_suberror_Unknown_NCLX_matrix_coefficients SuberrorInvalidJ2KCodestream = C.heif_suberror_Invalid_J2K_codestream SuberrorNoVcCBox = C.heif_suberror_No_vvcC_box SuberrorNoIcbrBox = C.heif_suberror_No_icbr_box // --- Unsupported_feature --- // Image was coded with an unsupported compression method. SuberrorUnsupportedCodec = C.heif_suberror_Unsupported_codec // Image is specified in an unknown way, e.g. as tiled grid image (which is supported) SuberrorUnsupportedImageType = C.heif_suberror_Unsupported_image_type SuberrorUnsupportedDataVersion = C.heif_suberror_Unsupported_data_version SuberrorUnsupportedGenericCompressionMethod = C.heif_suberror_Unsupported_generic_compression_method // The conversion of the source image to the requested chroma / colorspace is not supported. SuberrorUnsupportedColorConversion = C.heif_suberror_Unsupported_color_conversion SuberrorUnsupportedItemConstructionMethod = C.heif_suberror_Unsupported_item_construction_method SuberrorUnsupportedHeaderCompressionMethod = C.heif_suberror_Unsupported_header_compression_method // --- Encoder_plugin_error --- SuberrorUnsupportedBitDepth = C.heif_suberror_Unsupported_bit_depth // --- Encoding_error --- SuberrorCannotWriteOutputData = C.heif_suberror_Cannot_write_output_data SuberrorEncoderInitialization = C.heif_suberror_Encoder_initialization SuberrorEncoderEncoding = C.heif_suberror_Encoder_encoding SuberrorEncoderCleanup = C.heif_suberror_Encoder_cleanup SuberrorTooManyRegions = C.heif_suberror_Too_many_regions ) type HeifError struct { Code ErrorCode Subcode ErrorSubcode Message string } func (e *HeifError) Error() string { return e.Message } func convertHeifError(cerror C.struct_heif_error) error { if cerror.code == ErrorOK { return nil } return &HeifError{ Code: ErrorCode(cerror.code), Subcode: ErrorSubcode(cerror.subcode), Message: C.GoString(cerror.message), } } func convertItemIDs(ids []C.heif_item_id, count int) []int { result := make([]int, count) for i := 0; i < count; i++ { result[i] = int(ids[i]) } return result } // --- Context type Context struct { context *C.struct_heif_context } func NewContext() (*Context, error) { ctx := &Context{ context: C.heif_context_alloc(), } if ctx.context == nil { return nil, fmt.Errorf("Could not allocate context") } runtime.SetFinalizer(ctx, freeHeifContext) return ctx, nil } func freeHeifContext(c *Context) { C.heif_context_free(c.context) c.context = nil } func (c *Context) ReadFromFile(filename string) error { c_filename := C.CString(filename) defer C.free(unsafe.Pointer(c_filename)) err := C.heif_context_read_from_file(c.context, c_filename, nil) runtime.KeepAlive(c) return convertHeifError(err) } func (c *Context) ReadFromMemory(data []byte) error { // TODO: Use reader API internally. err := C.heif_context_read_from_memory(c.context, unsafe.Pointer(&data[0]), C.size_t(len(data)), nil) runtime.KeepAlive(c) return convertHeifError(err) } type Encoder struct { encoder *C.struct_heif_encoder id string name string } func (e *Encoder) ID() string { return e.id } func (e *Encoder) Name() string { return e.name } func (e *Encoder) SetQuality(q int) error { err := C.heif_encoder_set_lossy_quality(e.encoder, C.int(q)) runtime.KeepAlive(e) return convertHeifError(err) } func (e *Encoder) SetLossless(l LosslessMode) error { err := C.heif_encoder_set_lossless(e.encoder, C.int(l)) runtime.KeepAlive(e) return convertHeifError(err) } func (e *Encoder) SetLoggingLevel(l LoggingLevel) error { err := C.heif_encoder_set_logging_level(e.encoder, C.int(l)) runtime.KeepAlive(e) return convertHeifError(err) } func freeHeifEncoder(enc *Encoder) { C.heif_encoder_release(enc.encoder) enc.encoder = nil } func (c *Context) convertEncoderDescriptor(d *C.struct_heif_encoder_descriptor) (*Encoder, error) { cid := C.heif_encoder_descriptor_get_id_name(d) cname := C.heif_encoder_descriptor_get_name(d) enc := &Encoder{ id: C.GoString(cid), name: C.GoString(cname), } err := C.heif_context_get_encoder(c.context, d, &enc.encoder) runtime.KeepAlive(c) if err := convertHeifError(err); err != nil { return nil, err } runtime.SetFinalizer(enc, freeHeifEncoder) return enc, nil } func (c *Context) NewEncoder(compression Compression) (*Encoder, error) { const max = 1 descriptors := make([]*C.struct_heif_encoder_descriptor, max) num := int(C.heif_context_get_encoder_descriptors(c.context, uint32(compression), nil, &descriptors[0], C.int(max))) runtime.KeepAlive(c) if num == 0 { return nil, fmt.Errorf("no encoder for compression %v", compression) } return c.convertEncoderDescriptor(descriptors[0]) } func (c *Context) WriteToFile(filename string) error { err := C.heif_context_write_to_file(c.context, C.CString(filename)) runtime.KeepAlive(c) return convertHeifError(err) } func (c *Context) GetNumberOfTopLevelImages() int { i := int(C.heif_context_get_number_of_top_level_images(c.context)) runtime.KeepAlive(c) return i } func (c *Context) IsTopLevelImageID(ID int) bool { ok := C.heif_context_is_top_level_image_ID(c.context, C.heif_item_id(ID)) != 0 runtime.KeepAlive(c) return ok } func (c *Context) GetListOfTopLevelImageIDs() []int { num := int(C.heif_context_get_number_of_top_level_images(c.context)) runtime.KeepAlive(c) if num == 0 { return []int{} } origIDs := make([]C.heif_item_id, num) C.heif_context_get_list_of_top_level_image_IDs(c.context, &origIDs[0], C.int(num)) runtime.KeepAlive(c) return convertItemIDs(origIDs, num) } func (c *Context) GetPrimaryImageID() (int, error) { var id C.heif_item_id err := C.heif_context_get_primary_image_ID(c.context, &id) runtime.KeepAlive(c) if err := convertHeifError(err); err != nil { return 0, err } return int(id), nil } // --- ImageHandle type ImageHandle struct { handle *C.struct_heif_image_handle } func freeHeifImageHandle(c *ImageHandle) { C.heif_image_handle_release(c.handle) c.handle = nil } func (c *Context) GetPrimaryImageHandle() (*ImageHandle, error) { var handle ImageHandle err := C.heif_context_get_primary_image_handle(c.context, &handle.handle) runtime.KeepAlive(c) if err := convertHeifError(err); err != nil { return nil, err } runtime.SetFinalizer(&handle, freeHeifImageHandle) return &handle, convertHeifError(err) } func (c *Context) GetImageHandle(id int) (*ImageHandle, error) { var handle ImageHandle err := C.heif_context_get_image_handle(c.context, C.heif_item_id(id), &handle.handle) runtime.KeepAlive(c) if err := convertHeifError(err); err != nil { return nil, err } runtime.SetFinalizer(&handle, freeHeifImageHandle) return &handle, nil } func (h *ImageHandle) IsPrimaryImage() bool { ok := C.heif_image_handle_is_primary_image(h.handle) != 0 runtime.KeepAlive(h) return ok } func (h *ImageHandle) GetWidth() int { i := int(C.heif_image_handle_get_width(h.handle)) runtime.KeepAlive(h) return i } func (h *ImageHandle) GetHeight() int { i := int(C.heif_image_handle_get_height(h.handle)) runtime.KeepAlive(h) return i } func (h *ImageHandle) HasAlphaChannel() bool { ok := C.heif_image_handle_has_alpha_channel(h.handle) != 0 runtime.KeepAlive(h) return ok } func (h *ImageHandle) HasDepthImage() bool { ok := C.heif_image_handle_has_depth_image(h.handle) != 0 runtime.KeepAlive(h) return ok } func (h *ImageHandle) GetNumberOfDepthImages() int { i := int(C.heif_image_handle_get_number_of_depth_images(h.handle)) runtime.KeepAlive(h) return i } func (h *ImageHandle) GetListOfDepthImageIDs() []int { num := int(C.heif_image_handle_get_number_of_depth_images(h.handle)) runtime.KeepAlive(h) if num == 0 { return []int{} } origIDs := make([]C.heif_item_id, num) C.heif_image_handle_get_list_of_depth_image_IDs(h.handle, &origIDs[0], C.int(num)) runtime.KeepAlive(h) return convertItemIDs(origIDs, num) } func (h *ImageHandle) GetDepthImageHandle(depth_image_id int) (*ImageHandle, error) { var handle ImageHandle err := C.heif_image_handle_get_depth_image_handle(h.handle, C.heif_item_id(depth_image_id), &handle.handle) runtime.KeepAlive(h) if err := convertHeifError(err); err != nil { return nil, err } runtime.SetFinalizer(&handle, freeHeifImageHandle) return &handle, nil } func (h *ImageHandle) GetNumberOfThumbnails() int { i := int(C.heif_image_handle_get_number_of_thumbnails(h.handle)) runtime.KeepAlive(h) return i } func (h *ImageHandle) GetListOfThumbnailIDs() []int { num := int(C.heif_image_handle_get_number_of_thumbnails(h.handle)) runtime.KeepAlive(h) if num == 0 { return []int{} } origIDs := make([]C.heif_item_id, num) C.heif_image_handle_get_list_of_thumbnail_IDs(h.handle, &origIDs[0], C.int(num)) runtime.KeepAlive(h) return convertItemIDs(origIDs, num) } func (h *ImageHandle) GetThumbnail(thumbnail_id int) (*ImageHandle, error) { var handle ImageHandle err := C.heif_image_handle_get_thumbnail(h.handle, C.heif_item_id(thumbnail_id), &handle.handle) runtime.KeepAlive(h) runtime.SetFinalizer(&handle, freeHeifImageHandle) return &handle, convertHeifError(err) } // TODO: EXIF metadata // --- Image type DecodingOptions struct { options *C.struct_heif_decoding_options } func NewDecodingOptions() (*DecodingOptions, error) { options := &DecodingOptions{ options: C.heif_decoding_options_alloc(), } if options.options == nil { return nil, fmt.Errorf("Could not allocate decoding options") } runtime.SetFinalizer(options, freeHeifDecodingOptions) return options, nil } func freeHeifDecodingOptions(options *DecodingOptions) { C.heif_decoding_options_free(options.options) options.options = nil } type Image struct { image *C.struct_heif_image } func NewImage(width, height int, colorspace Colorspace, chroma Chroma) (*Image, error) { var image Image err := C.heif_image_create(C.int(width), C.int(height), uint32(colorspace), uint32(chroma), &image.image) if err := convertHeifError(err); err != nil { return nil, err } runtime.SetFinalizer(&image, freeHeifImage) return &image, nil } func freeHeifImage(image *Image) { C.heif_image_release(image.image) image.image = nil } func (h *ImageHandle) DecodeImage(colorspace Colorspace, chroma Chroma, options *DecodingOptions) (*Image, error) { var image Image var opt *C.struct_heif_decoding_options if options != nil { opt = options.options } err := C.heif_decode_image(h.handle, &image.image, uint32(colorspace), uint32(chroma), opt) runtime.KeepAlive(h) if err := convertHeifError(err); err != nil { return nil, err } runtime.SetFinalizer(&image, freeHeifImage) return &image, nil } func (img *Image) GetColorspace() Colorspace { cs := Colorspace(C.heif_image_get_colorspace(img.image)) runtime.KeepAlive(img) return cs } func (img *Image) GetChromaFormat() Chroma { c := Chroma(C.heif_image_get_chroma_format(img.image)) runtime.KeepAlive(img) return c } func (img *Image) GetWidth(channel Channel) int { i := int(C.heif_image_get_width(img.image, uint32(channel))) runtime.KeepAlive(img) return i } func (img *Image) GetHeight(channel Channel) int { i := int(C.heif_image_get_height(img.image, uint32(channel))) runtime.KeepAlive(img) return i } func (img *Image) GetBitsPerPixel(channel Channel) int { i := int(C.heif_image_get_bits_per_pixel(img.image, uint32(channel))) runtime.KeepAlive(img) return i } func (img *Image) GetBitsPerPixelRange(channel Channel) int { i := int(C.heif_image_get_bits_per_pixel_range(img.image, uint32(channel))) runtime.KeepAlive(img) return i } func (img *Image) GetImage() (image.Image, error) { var i image.Image cf := img.GetChromaFormat() switch cs := img.GetColorspace(); cs { case ColorspaceYCbCr: var subsample image.YCbCrSubsampleRatio switch cf { case Chroma420: subsample = image.YCbCrSubsampleRatio420 case Chroma422: subsample = image.YCbCrSubsampleRatio422 case Chroma444: subsample = image.YCbCrSubsampleRatio444 default: return nil, fmt.Errorf("Unsupported YCbCr chroma format: %v", cf) } y, err := img.GetPlane(ChannelY) if err != nil { return nil, err } cb, err := img.GetPlane(ChannelCb) if err != nil { return nil, err } cr, err := img.GetPlane(ChannelCr) if err != nil { return nil, err } i = &image.YCbCr{ Y: y.Plane, Cb: cb.Plane, Cr: cr.Plane, YStride: y.Stride, CStride: cb.Stride, SubsampleRatio: subsample, Rect: image.Rectangle{ Min: image.Point{ X: 0, Y: 0, }, Max: image.Point{ X: img.GetWidth(ChannelY), Y: img.GetHeight(ChannelY), }, }, } case ColorspaceRGB: switch cf { case Chroma444: r, err := img.GetPlane(ChannelR) if err != nil { return nil, err } g, err := img.GetPlane(ChannelG) if err != nil { return nil, err } b, err := img.GetPlane(ChannelB) if err != nil { return nil, err } width := img.GetWidth(ChannelR) height := img.GetHeight(ChannelR) read_pos_r := 0 read_pos_g := 0 read_pos_b := 0 write_pos := 0 var rgba []byte var stride int if bpp := img.GetBitsPerPixelRange(ChannelR); bpp > 8 { // NOTE: We only support the same bits per pixel on all components. stride = width * 8 rgba = make([]byte, height*stride) stride_add_r := r.Stride - width*2 stride_add_g := g.Stride - width*2 stride_add_b := b.Stride - width*2 if bpp == 16 { for y := 0; y < height; y++ { for x := 0; x < width; x++ { rgba[write_pos] = r.Plane[read_pos_r] rgba[write_pos+1] = r.Plane[read_pos_r+1] rgba[write_pos+2] = g.Plane[read_pos_g] rgba[write_pos+3] = g.Plane[read_pos_g+1] rgba[write_pos+4] = b.Plane[read_pos_b] rgba[write_pos+5] = b.Plane[read_pos_b+1] rgba[write_pos+6] = 0xff rgba[write_pos+7] = 0xff read_pos_r += 2 read_pos_g += 2 read_pos_b += 2 write_pos += 8 } read_pos_r += stride_add_r read_pos_g += stride_add_g read_pos_b += stride_add_b } } else { for y := 0; y < height; y++ { for x := 0; x < width; x++ { r_value := (int16(r.Plane[read_pos_r+1]) << 8) | int16(r.Plane[read_pos_r]) r_value = (r_value << (16 - uint(bpp))) | (r_value >> (2*uint(bpp) - 16)) rgba[write_pos] = byte(r_value >> 8) rgba[write_pos+1] = byte(r_value & 0xff) g_value := (int16(g.Plane[read_pos_g+1]) << 8) | int16(g.Plane[read_pos_g]) g_value = (g_value << (16 - uint(bpp))) | (g_value >> (2*uint(bpp) - 16)) rgba[write_pos+2] = byte(g_value >> 8) rgba[write_pos+3] = byte(g_value & 0xff) b_value := (int16(b.Plane[read_pos_b+1]) << 8) | int16(b.Plane[read_pos_b]) b_value = (b_value << (16 - uint(bpp))) | (b_value >> (2*uint(bpp) - 16)) rgba[write_pos+4] = byte(b_value >> 8) rgba[write_pos+5] = byte(b_value & 0xff) rgba[write_pos+6] = 0xff rgba[write_pos+7] = 0xff read_pos_r += 2 read_pos_g += 2 read_pos_b += 2 write_pos += 8 } read_pos_r += stride_add_r read_pos_g += stride_add_g read_pos_b += stride_add_b } } i = &image.RGBA64{ Pix: rgba, Stride: stride, Rect: image.Rectangle{ Min: image.Point{ X: 0, Y: 0, }, Max: image.Point{ X: width, Y: height, }, }, } } else { stride = width * 4 rgba = make([]byte, height*stride) stride_add_r := r.Stride - width stride_add_g := g.Stride - width stride_add_b := b.Stride - width for y := 0; y < height; y++ { for x := 0; x < width; x++ { rgba[write_pos] = r.Plane[read_pos_r] rgba[write_pos+1] = g.Plane[read_pos_g] rgba[write_pos+2] = b.Plane[read_pos_b] rgba[write_pos+3] = 0xff read_pos_r++ read_pos_g++ read_pos_b++ write_pos += 4 } read_pos_r += stride_add_r read_pos_g += stride_add_g read_pos_b += stride_add_b } i = &image.RGBA{ Pix: rgba, Stride: stride, Rect: image.Rectangle{ Min: image.Point{ X: 0, Y: 0, }, Max: image.Point{ X: width, Y: height, }, }, } } case ChromaInterleavedRGB: rgb, err := img.GetPlane(ChannelInterleaved) if err != nil { return nil, err } width := img.GetWidth(ChannelInterleaved) height := img.GetHeight(ChannelInterleaved) rgba := make([]byte, width*height*4) read_pos := 0 write_pos := 0 stride_add := rgb.Stride - width*3 for y := 0; y < height; y++ { for x := 0; x < width; x++ { rgba[write_pos] = rgb.Plane[read_pos] rgba[write_pos+1] = rgb.Plane[read_pos+1] rgba[write_pos+2] = rgb.Plane[read_pos+2] rgba[write_pos+3] = 0xff read_pos += 3 write_pos += 4 } read_pos += stride_add } i = &image.RGBA{ Pix: rgba, Stride: width * 4, Rect: image.Rectangle{ Min: image.Point{ X: 0, Y: 0, }, Max: image.Point{ X: width, Y: height, }, }, } case ChromaInterleavedRGBA: rgba, err := img.GetPlane(ChannelInterleaved) if err != nil { return nil, err } i = &image.RGBA{ Pix: rgba.Plane, Stride: rgba.Stride, Rect: image.Rectangle{ Min: image.Point{ X: 0, Y: 0, }, Max: image.Point{ X: img.GetWidth(ChannelInterleaved), Y: img.GetHeight(ChannelInterleaved), }, }, } case ChromaInterleavedRRGGBB_BE: rgb, err := img.GetPlane(ChannelInterleaved) if err != nil { return nil, err } width := img.GetWidth(ChannelInterleaved) height := img.GetHeight(ChannelInterleaved) rgba := make([]byte, width*height*8) read_pos := 0 write_pos := 0 stride_add := rgb.Stride - width*6 if bpp := img.GetBitsPerPixelRange(ChannelInterleaved); bpp != 16 { for y := 0; y < height; y++ { for x := 0; x < width; x++ { r_value := (int16(rgb.Plane[read_pos]) << 8) | int16(rgb.Plane[read_pos+1]) r_value = (r_value << (16 - uint(bpp))) | (r_value >> (2*uint(bpp) - 16)) rgba[write_pos] = byte(r_value >> 8) rgba[write_pos+1] = byte(r_value & 0xff) g_value := (int16(rgb.Plane[read_pos+2]) << 8) | int16(rgb.Plane[read_pos+3]) g_value = (g_value << (16 - uint(bpp))) | (g_value >> (2*uint(bpp) - 16)) rgba[write_pos+2] = byte(g_value >> 8) rgba[write_pos+3] = byte(g_value & 0xff) b_value := (int16(rgb.Plane[read_pos+4]) << 8) | int16(rgb.Plane[read_pos+5]) b_value = (b_value << (16 - uint(bpp))) | (b_value >> (2*uint(bpp) - 16)) rgba[write_pos+4] = byte(b_value >> 8) rgba[write_pos+5] = byte(b_value & 0xff) rgba[write_pos+6] = 0xff rgba[write_pos+7] = 0xff read_pos += 6 write_pos += 8 } read_pos += stride_add } } else { for y := 0; y < height; y++ { for x := 0; x < width; x++ { rgba[write_pos] = rgb.Plane[read_pos] rgba[write_pos+1] = rgb.Plane[read_pos+1] rgba[write_pos+2] = rgb.Plane[read_pos+2] rgba[write_pos+3] = rgb.Plane[read_pos+3] rgba[write_pos+4] = rgb.Plane[read_pos+4] rgba[write_pos+5] = rgb.Plane[read_pos+5] rgba[write_pos+6] = 0xff rgba[write_pos+7] = 0xff read_pos += 6 write_pos += 8 } read_pos += stride_add } } i = &image.RGBA64{ Pix: rgba, Stride: width * 4, Rect: image.Rectangle{ Min: image.Point{ X: 0, Y: 0, }, Max: image.Point{ X: width, Y: height, }, }, } case ChromaInterleavedRRGGBBAA_BE: rgba, err := img.GetPlane(ChannelInterleaved) if err != nil { return nil, err } width := img.GetWidth(ChannelInterleaved) height := img.GetHeight(ChannelInterleaved) var plane []byte if bpp := img.GetBitsPerPixelRange(ChannelInterleaved); bpp != 16 { read_pos := 0 write_pos := 0 stride_add := rgba.Stride - width*8 plane = make([]byte, width*height*8) for y := 0; y < height; y++ { for x := 0; x < width; x++ { r_value := (int16(rgba.Plane[read_pos]) << 8) | int16(rgba.Plane[read_pos+1]) r_value = (r_value << (16 - uint(bpp))) | (r_value >> (2*uint(bpp) - 16)) plane[write_pos] = byte(r_value >> 8) plane[write_pos+1] = byte(r_value & 0xff) g_value := (int16(rgba.Plane[read_pos+2]) << 8) | int16(rgba.Plane[read_pos+3]) g_value = (g_value << (16 - uint(bpp))) | (g_value >> (2*uint(bpp) - 16)) plane[write_pos+2] = byte(g_value >> 8) plane[write_pos+3] = byte(g_value & 0xff) b_value := (int16(rgba.Plane[read_pos+4]) << 8) | int16(rgba.Plane[read_pos+5]) b_value = (b_value << (16 - uint(bpp))) | (b_value >> (2*uint(bpp) - 16)) plane[write_pos+4] = byte(b_value >> 8) plane[write_pos+5] = byte(b_value & 0xff) a_value := (int16(rgba.Plane[read_pos+6]) << 8) | int16(rgba.Plane[read_pos+7]) a_value = (a_value << (16 - uint(bpp))) | (a_value >> (2*uint(bpp) - 16)) plane[write_pos+6] = byte(a_value >> 8) plane[write_pos+7] = byte(a_value & 0xff) read_pos += 8 write_pos += 8 } read_pos += stride_add } } else { plane = rgba.Plane } i = &image.RGBA64{ Pix: plane, Stride: rgba.Stride, Rect: image.Rectangle{ Min: image.Point{ X: 0, Y: 0, }, Max: image.Point{ X: width, Y: height, }, }, } default: return nil, fmt.Errorf("Unsupported RGB chroma format: %v", cf) } default: return nil, fmt.Errorf("Unsupported colorspace: %v", cs) } return i, nil } type ImageAccess struct { Plane []byte planePtr unsafe.Pointer Stride int height int image *Image // need this reference to make sure the image is not GC'ed while we access it } func (i *ImageAccess) setData(data []byte, stride int) { // Handle common case directly if stride == i.Stride { dstP := uintptr(i.planePtr) srcP := uintptr(unsafe.Pointer(&data[0])) C.memcpy(unsafe.Pointer(dstP), unsafe.Pointer(srcP), C.size_t(i.height*stride)) } else { for y := 0; y < i.height; y++ { dstP := uintptr(i.planePtr) + uintptr(y*i.Stride) srcP := uintptr(unsafe.Pointer(&data[0])) + uintptr(y*stride) C.memcpy(unsafe.Pointer(dstP), unsafe.Pointer(srcP), C.size_t(stride)) } } i.Plane = C.GoBytes(i.planePtr, C.int(i.height*i.Stride)) } func (img *Image) GetPlane(channel Channel) (*ImageAccess, error) { height := C.heif_image_get_height(img.image, uint32(channel)) runtime.KeepAlive(img) if height == -1 { return nil, fmt.Errorf("No such channel %v", channel) } var stride C.int plane := C.heif_image_get_plane(img.image, uint32(channel), &stride) runtime.KeepAlive(img) if plane == nil { return nil, fmt.Errorf("No such channel %v", channel) } ptr := unsafe.Pointer(plane) size := stride * height access := &ImageAccess{ Plane: C.GoBytes(ptr, size), planePtr: ptr, Stride: int(stride), height: int(height), image: img, } return access, nil } func (img *Image) NewPlane(channel Channel, width, height, depth int) (*ImageAccess, error) { err := C.heif_image_add_plane(img.image, uint32(channel), C.int(width), C.int(height), C.int(depth)) runtime.KeepAlive(img) if err := convertHeifError(err); err != nil { return nil, err } return img.GetPlane(channel) } func (img *Image) ScaleImage(width int, height int) (*Image, error) { var scaled_image Image err := C.heif_image_scale_image(img.image, &scaled_image.image, C.int(width), C.int(height), nil) runtime.KeepAlive(img) if err := convertHeifError(err); err != nil { return nil, err } runtime.SetFinalizer(&scaled_image, freeHeifImage) return &scaled_image, nil } // --- High-level encoding API. type EncodingOptions struct { options *C.struct_heif_encoding_options } func NewEncodingOptions() (*EncodingOptions, error) { options := &EncodingOptions{ options: C.heif_encoding_options_alloc(), } if options.options == nil { return nil, fmt.Errorf("Could not allocate encoding options") } runtime.SetFinalizer(options, freeHeifEncodingOptions) return options, nil } func freeHeifEncodingOptions(options *EncodingOptions) { C.heif_encoding_options_free(options.options) options.options = nil } func imageFromRGBA(i *image.RGBA) (*Image, error) { min := i.Bounds().Min max := i.Bounds().Max w := max.X - min.X h := max.Y - min.Y out, err := NewImage(w, h, ColorspaceRGB, ChromaInterleavedRGBA) if err != nil { return nil, fmt.Errorf("failed to create image: %v", err) } p, err := out.NewPlane(ChannelInterleaved, w, h, 8) if err != nil { return nil, fmt.Errorf("failed to add plane: %v", err) } p.setData([]byte(i.Pix), w*4) return out, nil } func imageFromRGBA64(i *image.RGBA64) (*Image, error) { min := i.Bounds().Min max := i.Bounds().Max w := max.X - min.X h := max.Y - min.Y out, err := NewImage(w, h, ColorspaceRGB, ChromaInterleavedRRGGBBAA_BE) if err != nil { return nil, fmt.Errorf("failed to create image: %v", err) } p, err := out.NewPlane(ChannelInterleaved, w, h, 10) if err != nil { return nil, fmt.Errorf("failed to add plane: %v", err) } pix := make([]byte, w*h*8) read_pos := 0 write_pos := 0 for y := 0; y < h; y++ { for x := 0; x < w; x++ { r := (uint16(i.Pix[read_pos]) << 8) | uint16(i.Pix[read_pos+1]) r = r >> 6 pix[write_pos] = byte(r >> 8) pix[write_pos+1] = byte(r & 0xff) read_pos += 2 g := (uint16(i.Pix[read_pos]) << 8) | uint16(i.Pix[read_pos+1]) g = g >> 6 pix[write_pos+2] = byte(g >> 8) pix[write_pos+3] = byte(g & 0xff) read_pos += 2 b := (uint16(i.Pix[read_pos]) << 8) | uint16(i.Pix[read_pos+1]) b = b >> 6 pix[write_pos+4] = byte(b >> 8) pix[write_pos+5] = byte(b & 0xff) read_pos += 2 a := (uint16(i.Pix[read_pos]) << 8) | uint16(i.Pix[read_pos+1]) a = a >> 6 pix[write_pos+6] = byte(a >> 8) pix[write_pos+7] = byte(a & 0xff) pix[write_pos+6] = byte(a >> 8) pix[write_pos+7] = byte(a & 0xff) read_pos += 2 write_pos += 8 } } p.setData(pix, w*8) return out, nil } func imageFromGray(i *image.Gray) (*Image, error) { min := i.Bounds().Min max := i.Bounds().Max w := max.X - min.X h := max.Y - min.Y out, err := NewImage(w, h, ColorspaceYCbCr, ChromaMonochrome) if err != nil { return nil, fmt.Errorf("failed to create image: %v", err) } const depth = 8 pY, err := out.NewPlane(ChannelY, w, h, depth) if err != nil { return nil, fmt.Errorf("failed to add Y plane: %v", err) } pY.setData([]byte(i.Pix), i.Stride) return out, nil } func imageFromYCbCr(i *image.YCbCr) (*Image, error) { min := i.Bounds().Min max := i.Bounds().Max w := max.X - min.X h := max.Y - min.Y var cm Chroma switch sr := i.SubsampleRatio; sr { case image.YCbCrSubsampleRatio420: cm = Chroma420 default: return nil, fmt.Errorf("unsupported subsample ratio: %s", sr.String()) } out, err := NewImage(w, h, ColorspaceYCbCr, cm) if err != nil { return nil, fmt.Errorf("failed to create image: %v", err) } const depth = 8 pY, err := out.NewPlane(ChannelY, w, h, depth) if err != nil { return nil, fmt.Errorf("failed to add Y plane: %v", err) } pY.setData([]byte(i.Y), i.YStride) // TODO: Might need to be updated for other SubsampleRatio values. halfW, halfH := (w+1)/2, (h+1)/2 pCb, err := out.NewPlane(ChannelCb, halfW, halfH, depth) if err != nil { return nil, fmt.Errorf("failed to add Cb plane: %v", err) } pCb.setData([]byte(i.Cb), i.CStride) pCr, err := out.NewPlane(ChannelCr, halfW, halfH, depth) if err != nil { return nil, fmt.Errorf("failed to add Cr plane: %v", err) } pCr.setData([]byte(i.Cr), i.CStride) return out, nil } func EncodeFromImage(img image.Image, compression Compression, quality int, lossless LosslessMode, logging LoggingLevel) (*Context, error) { var out *Image switch i := img.(type) { default: return 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) } out = tmp case *image.RGBA64: tmp, err := imageFromRGBA64(i) if err != nil { return 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) } out = tmp case *image.YCbCr: tmp, err := imageFromYCbCr(i) if err != nil { return 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) } enc, err := ctx.NewEncoder(compression) if err != nil { return nil, fmt.Errorf("failed to create encoder: %v", err) } if err := enc.SetQuality(quality); err != nil { return nil, fmt.Errorf("failed to set quality: %v", err) } if err := enc.SetLossless(lossless); err != nil { return nil, fmt.Errorf("failed to set lossless mode: %v", err) } if err := enc.SetLoggingLevel(logging); err != nil { return nil, fmt.Errorf("failed to set logging level: %v", err) } encOpts, err := NewEncodingOptions() if err != nil { return nil, fmt.Errorf("failed to get encoding options: %v", err) } var handle ImageHandle err2 := C.heif_context_encode_image(ctx.context, out.image, enc.encoder, encOpts.options, &handle.handle) runtime.KeepAlive(ctx) runtime.KeepAlive(out) runtime.KeepAlive(enc) runtime.KeepAlive(encOpts) if err := convertHeifError(err2); err != nil { return nil, fmt.Errorf("failed to encode image: %v", err) } runtime.SetFinalizer(&handle, freeHeifImageHandle) return ctx, nil } // --- High-level decoding API, always decodes primary image (if present). func decodePrimaryImageFromReader(r io.Reader) (*ImageHandle, error) { ctx, err := NewContext() if err != nil { return nil, err } data, err := ioutil.ReadAll(r) if err != nil { return nil, err } if err := ctx.ReadFromMemory(data); err != nil { return nil, err } handle, err := ctx.GetPrimaryImageHandle() if err != nil { return nil, err } return handle, nil } func decodeImage(r io.Reader) (image.Image, error) { handle, err := decodePrimaryImageFromReader(r) if err != nil { return nil, err } img, err := handle.DecodeImage(ColorspaceUndefined, ChromaUndefined, nil) if err != nil { return nil, err } return img.GetImage() } func decodeConfig(r io.Reader) (image.Config, error) { var config image.Config handle, err := decodePrimaryImageFromReader(r) if err != nil { return config, err } config = image.Config{ ColorModel: color.YCbCrModel, Width: handle.GetWidth(), Height: handle.GetHeight(), } return config, nil } func init() { image.RegisterFormat("heif", "????ftypheic", decodeImage, decodeConfig) image.RegisterFormat("heif", "????ftypheim", decodeImage, decodeConfig) image.RegisterFormat("heif", "????ftypheis", decodeImage, decodeConfig) image.RegisterFormat("heif", "????ftypheix", decodeImage, decodeConfig) image.RegisterFormat("heif", "????ftyphevc", decodeImage, decodeConfig) image.RegisterFormat("heif", "????ftyphevm", decodeImage, decodeConfig) image.RegisterFormat("heif", "????ftyphevs", decodeImage, decodeConfig) image.RegisterFormat("heif", "????ftypmif1", decodeImage, decodeConfig) image.RegisterFormat("avif", "????ftypavif", decodeImage, decodeConfig) image.RegisterFormat("avif", "????ftypavis", decodeImage, decodeConfig) }