Search code examples
delphiheif

How can I write a heif image to a byte array with libheif?


I'm translating the libheif to pascal code using a dll, for this Delphi library. And I have been able to translate most needed functions, with the sole exception of writing a heif image to a byte array.

Reading a heif image as so is very simple to do, with the help of heif_context_read_from_memory. But there is no equivalent heif_context_write_to_memory function.

According to this issue. The library should be able to write a heif image using heif_context_write and providing a writing callback. And I have been able to successfully add such a implementation. Which is as follows:

const
  InputFile = 'C:\Test\sample.heic';
  OutputFile = 'output.heic';

var
  databytes: PByte;
  writer: THeifWriter;

var
  ctx: PHeifContext;
  imageHandle: PHeifImageHandle;
  encoder: PHeifEncoder;

  FImage: PHeifImage;
  FDataStride: integer;
begin
  // Allocate context
  ctx := heif_context_alloc();
  try
    // Make new memory instance
    heif_context_read_from_file(ctx, @AnsiString(InputFile)[1], nil).ErrRaise;

    // Get primary image
    heif_context_get_primary_image_handle(ctx, imageHandle).ErrRaise;
    try
      // Decode image
      heif_decode_image(imageHandle, FImage, THeifColorspace.colorspace_RGB, THeifChroma.chroma_interleaved_RGB, nil).ErrRaise;

      // Read plane
      heif_image_get_plane(FImage, THeifChannel.channel_interleaved, FDataStride);
    finally
      heif_image_handle_release(imageHandle);
    end;
  finally
    heif_context_free( ctx );
  end;

  // Encode
  ctx := heif_context_alloc();
  try
    // Crete encoder
    heif_context_get_encoder_for_format(ctx, THeifCompressionFormat.compression_HEVC, encoder).ErrRaise;
    try
      // Set quality
      heif_encoder_set_lossless(encoder, true).ErrRaise;

      // Encode
      heif_context_encode_image(ctx, FImage, encoder, nil, nil).ErrRaise;
    finally
      heif_encoder_release(encoder);
    end;

    // Write
    databytes := AllocMem(sizeof(byte));
    databytes^ := 123;

    writer.writer_api_version := 1;
    writer.write := writer_write;
    heif_context_write(ctx, writer, databytes);
  finally
    heif_context_free(ctx);
  end;

And the callback procedure:

function TForm1.writer_write(ctx: PHeifContext; const data: Pointer;
  size: cardinal; userdata: Pointer): THeifError;
var
  value: int64;
  MemoryZone: PByte;
begin
  // Allocate memory
  MemoryZone := AllocMem(size);
  try
    Move(data, MemoryZone, size);
  finally
    FreeMem(MemoryZone, size);
  end;

  // Result
  Result := THeifError.Create(THeifErrorNum.heif_error_Invalid_input);
end;

But the Move() procedure in the callback gives me an error. And the size cardinal always seems to be a bit random.

And It's not the encoder of the file. They are both read correctly. If I use heif_context_write_to_file(ctx, @AnsiString(OutputFile)[1]) instead of heif_context_write, the image is saved correctly.

I've scoured many examples and looked at the documentation. But everything I find is a bit lacking in explications on how that callback procedure even works.

And now I'm out of clues on what to do next.


Solution

  • The function DoWrite should be declared as class function DoWrite(ctx: PHeifContext; const data: Pointer; size: cardinal; userdata: Pointer): THeifError; static;

    If it is a regular method, then Delphi includes a hidden parameter that refers to the Delphi class instance. When declared as a static function this hidden parameter is omitted.

    In theory, you could just skip the ctx parameter in the callback function, but then you have the risk that you will crash if this function refers to anything in the class. So the class static function is a cleaner solution.