Commit 7d25548f1 for imagemagick.org

commit 7d25548f140d3f2c71cedf2772a3ddfcd3e11f7d
Author: Cristy <urban-warrior@imagemagick.org>
Date:   Sun Aug 30 10:24:06 2026 -0400

    framework for c2pa coder

diff --git a/coders/c2pa.c b/coders/c2pa.c
index f1d7aa000..49405aeb7 100644
--- a/coders/c2pa.c
+++ b/coders/c2pa.c
@@ -40,21 +40,23 @@
   Include declarations.
 */
 #include "MagickCore/studio.h"
-#include "MagickCore/attribute.h"
+#include "MagickCore/artifact.h"
 #include "MagickCore/blob.h"
 #include "MagickCore/blob-private.h"
 #include "MagickCore/constitute.h"
+#include "MagickCore/delegate.h"
 #include "MagickCore/exception.h"
 #include "MagickCore/exception-private.h"
+#include "MagickCore/image.h"
+#include "MagickCore/image-private.h"
 #include "MagickCore/list.h"
-#include "MagickCore/magick.h"
 #include "MagickCore/memory_.h"
-#include "MagickCore/monitor.h"
-#include "MagickCore/monitor-private.h"
-#include "MagickCore/quantum-private.h"
-#include "MagickCore/static.h"
-#include "MagickCore/string_.h"
 #include "MagickCore/module.h"
+#include "MagickCore/option.h"
+#include "MagickCore/resource_.h"
+#include "MagickCore/string_.h"
+#include "MagickCore/utility.h"
+#include "MagickCore/utility-private.h"

 /*
   Forward declarations.
@@ -67,7 +69,7 @@ static MagickBooleanType
 %                                                                             %
 %                                                                             %
 %                                                                             %
-%   R e a d C L I P I m a g e                                                 %
+%   R e a d C 2 P A I m a g e                                                 %
 %                                                                             %
 %                                                                             %
 %                                                                             %
@@ -90,9 +92,81 @@ static MagickBooleanType
 static Image *ReadC2PAImage(const ImageInfo *image_info,
   ExceptionInfo *exception)
 {
-  (void) image_info;
-  (void) exception;
-  return((Image *) NULL);
+  ExceptionInfo
+    *sans_exception;
+
+  Image
+    *image,
+    *images,
+    *next;
+
+  ImageInfo
+    *read_info;
+
+  MagickBooleanType
+    status;
+
+  /*
+    Open image file.
+  */
+  assert(image_info != (const ImageInfo *) NULL);
+  assert(image_info->signature == MagickCoreSignature);
+  assert(exception != (ExceptionInfo *) NULL);
+  assert(exception->signature == MagickCoreSignature);
+  if (IsEventLogging() != MagickFalse)
+    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
+      image_info->filename);
+  image=AcquireImage(image_info,exception);
+  status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
+  if (status == MagickFalse)
+    {
+      image=DestroyImageList(image);
+      return((Image *) NULL);
+    }
+  (void) CloseBlob(image);
+  /*
+    Blob support is disabled for this coder, hence the filename always
+    references a seekable file on disk that the delegate can open (%i).
+  */
+  read_info=CloneImageInfo(image_info);
+  SetImageInfoBlob(read_info,(void *) NULL,0);
+  read_info->temporary=MagickFalse;
+  (void) CopyMagickString(read_info->filename,image_info->filename,
+    MagickPathExtent);
+  (void) CopyMagickString(image->filename,image_info->filename,
+    MagickPathExtent);
+  status=InvokeDelegate(read_info,image,"c2pa:decode",(char *) NULL,exception);
+  image=DestroyImageList(image);
+  if (status == MagickFalse)
+    {
+      if (*read_info->unique != '\0')
+        (void) RelinquishUniqueFileResource(read_info->unique);
+      read_info=DestroyImageInfo(read_info);
+      return((Image *) NULL);
+    }
+  /*
+    Read the image the delegate returned in %o.
+  */
+  (void) CopyMagickString(read_info->filename,read_info->unique,
+    MagickPathExtent);
+  *read_info->magick='\0';
+  sans_exception=AcquireExceptionInfo();
+  (void) SetImageInfo(read_info,1,sans_exception);
+  sans_exception=DestroyExceptionInfo(sans_exception);
+  images=ReadImage(read_info,exception);
+  (void) RelinquishUniqueFileResource(read_info->unique);
+  read_info=DestroyImageInfo(read_info);
+  if (images == (Image *) NULL)
+    return((Image *) NULL);
+  for (next=images; next != (Image *) NULL; next=GetNextImageInList(next))
+  {
+    (void) CopyMagickString(next->filename,image_info->filename,
+      MagickPathExtent);
+    (void) CopyMagickString(next->magick,"C2PA",MagickPathExtent);
+    (void) CopyMagickString(next->magick_filename,image_info->filename,
+      MagickPathExtent);
+  }
+  return(GetFirstImageInList(images));
 }

 /*
@@ -100,7 +174,7 @@ static Image *ReadC2PAImage(const ImageInfo *image_info,
 %                                                                             %
 %                                                                             %
 %                                                                             %
-%   R e g i s t e r C L I P I m a g e                                         %
+%   R e g i s t e r C 2 P A I m a g e                                         %
 %                                                                             %
 %                                                                             %
 %                                                                             %
@@ -126,6 +200,10 @@ ModuleExport size_t RegisterC2PAImage(void)
   entry=AcquireMagickInfo("C2PA","C2PA","C2PA Provenance Metadata");
   entry->decoder=(DecodeImageHandler *) ReadC2PAImage;
   entry->encoder=(EncodeImageHandler *) WriteC2PAImage;
+  entry->flags|=CoderDecoderSeekableStreamFlag;
+  entry->flags|=CoderEncoderSeekableStreamFlag;
+  entry->flags^=CoderBlobSupportFlag;
+  entry->format_type=ExplicitFormatType;
   (void) RegisterMagickInfo(entry);
   return(MagickImageCoderSignature);
 }
@@ -135,7 +213,7 @@ ModuleExport size_t RegisterC2PAImage(void)
 %                                                                             %
 %                                                                             %
 %                                                                             %
-%   U n r e g i s t e r C L I P I m a g e                                     %
+%   U n r e g i s t e r C 2 P A I m a g e                                     %
 %                                                                             %
 %                                                                             %
 %                                                                             %
@@ -159,7 +237,7 @@ ModuleExport void UnregisterC2PAImage(void)
 %                                                                             %
 %                                                                             %
 %                                                                             %
-%   W r i t e C L I P I m a g e                                               %
+%   W r i t e C 2 P A I m a g e                                               %
 %                                                                             %
 %                                                                             %
 %                                                                             %
@@ -185,8 +263,183 @@ ModuleExport void UnregisterC2PAImage(void)
 static MagickBooleanType WriteC2PAImage(const ImageInfo *image_info,
   Image *image,ExceptionInfo *exception)
 {
-  (void) image_info;
-  (void) image;
-  (void) exception;
-  return(MagickFalse);
+  static const char
+    *credentials[] = { "c2pa:key", "c2pa:password", "c2pa:cert",
+      "c2pa:manifest", (const char *) NULL };
+
+  char
+    *buffer,
+    format[MagickPathExtent],
+    unique[MagickPathExtent];
+
+  const char
+    *option;
+
+  FILE
+    *file;
+
+  Image
+    *write_image;
+
+  ImageInfo
+    *write_info;
+
+  MagickBooleanType
+    status;
+
+  size_t
+    length,
+    quantum;
+
+  ssize_t
+    count,
+    i;
+
+  assert(image_info != (const ImageInfo *) NULL);
+  assert(image_info->signature == MagickCoreSignature);
+  assert(image != (Image *) NULL);
+  assert(image->signature == MagickCoreSignature);
+  assert(exception != (ExceptionInfo *) NULL);
+  assert(exception->signature == MagickCoreSignature);
+  if (IsEventLogging() != MagickFalse)
+    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
+  /*
+    A manifest is required to sign an image.
+  */
+  option=GetImageOption(image_info,"c2pa:manifest");
+  if (option == (const char *) NULL)
+    option=GetImageArtifact(image,"c2pa:manifest");
+  if (option == (const char *) NULL)
+    {
+      (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
+        "MustSpecifyImageManifest","`%s'",image->filename);
+      return(MagickFalse);
+    }
+  /*
+    Determine the intermediate image format the delegate signs.
+  */
+  option=GetImageOption(image_info,"c2pa:format");
+  if (option == (const char *) NULL)
+    option=GetImageArtifact(image,"c2pa:format");
+  if (option != (const char *) NULL)
+    (void) CopyMagickString(format,option,MagickPathExtent);
+  else
+    if ((*image->magick != '\0') &&
+        (LocaleCompare(image->magick,"C2PA") != 0))
+      (void) CopyMagickString(format,image->magick,MagickPathExtent);
+    else
+      (void) CopyMagickString(format,"PNG",MagickPathExtent);
+  /*
+    Write the image to a unique file the delegate signs (%i).
+  */
+  if (AcquireUniqueFilename(unique) == MagickFalse)
+    {
+      ThrowFileException(exception,FileOpenError,"UnableToCreateTemporaryFile",
+        image->filename);
+      return(MagickFalse);
+    }
+  write_image=CloneImage(image,0,0,MagickTrue,exception);
+  if (write_image == (Image *) NULL)
+    {
+      (void) RelinquishUniqueFileResource(unique);
+      return(MagickFalse);
+    }
+  write_info=CloneImageInfo(image_info);
+  SetImageInfoBlob(write_info,(void *) NULL,0);
+  write_info->temporary=MagickFalse;
+  write_info->adjoin=MagickTrue;
+  (void) CopyMagickString(write_info->magick,format,MagickPathExtent);
+  (void) FormatLocaleString(write_info->filename,MagickPathExtent,"%s:%s",
+    format,unique);
+  (void) CopyMagickString(write_image->filename,write_info->filename,
+    MagickPathExtent);
+  status=WriteImage(write_info,write_image,exception);
+  if (status == MagickFalse)
+    {
+      write_info=DestroyImageInfo(write_info);
+      write_image=DestroyImage(write_image);
+      (void) RelinquishUniqueFileResource(unique);
+      return(MagickFalse);
+    }
+  /*
+    Promote the signing credentials to artifacts so %[c2pa:*] resolves when
+    the delegate command is interpreted.
+  */
+  for (i=0; credentials[i] != (const char *) NULL; i++)
+  {
+    option=GetImageOption(image_info,credentials[i]);
+    if (option != (const char *) NULL)
+      (void) SetImageArtifact(write_image,credentials[i],option);
+  }
+  /*
+    Sign the image with the c2pa:encode delegate.
+  */
+  (void) CopyMagickString(write_info->filename,unique,MagickPathExtent);
+  (void) CopyMagickString(write_image->filename,unique,MagickPathExtent);
+  (void) CopyMagickString(write_image->magick,format,MagickPathExtent);
+  status=InvokeDelegate(write_info,write_image,(char *) NULL,"c2pa:encode",
+    exception);
+  write_image=DestroyImage(write_image);
+  (void) RelinquishUniqueFileResource(unique);
+  if (status == MagickFalse)
+    {
+      if (*write_info->unique != '\0')
+        (void) RelinquishUniqueFileResource(write_info->unique);
+      write_info=DestroyImageInfo(write_info);
+      return(MagickFalse);
+    }
+  /*
+    Copy the signed image the delegate returned in %o; the bytes are written
+    verbatim so the manifest and its signature are preserved.
+  */
+  file=fopen_utf8(write_info->unique,"rb");
+  if (file == (FILE *) NULL)
+    {
+      ThrowFileException(exception,FileOpenError,"UnableToOpenFile",
+        write_info->unique);
+      (void) RelinquishUniqueFileResource(write_info->unique);
+      write_info=DestroyImageInfo(write_info);
+      return(MagickFalse);
+    }
+  status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
+  if (status == MagickFalse)
+    {
+      (void) fclose(file);
+      (void) RelinquishUniqueFileResource(write_info->unique);
+      write_info=DestroyImageInfo(write_info);
+      return(MagickFalse);
+    }
+  quantum=(size_t) MagickMaxBufferExtent;
+  buffer=(char *) AcquireQuantumMemory(quantum,sizeof(*buffer));
+  if (buffer == (char *) NULL)
+    {
+      (void) fclose(file);
+      (void) CloseBlob(image);
+      (void) RelinquishUniqueFileResource(write_info->unique);
+      write_info=DestroyImageInfo(write_info);
+      ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
+        image->filename);
+    }
+  for (length=fread(buffer,1,quantum,file); length != 0; )
+  {
+    count=0;
+    for (i=0; i < (ssize_t) length; i+=count)
+    {
+      count=WriteBlob(image,length-(size_t) i,(unsigned char *) buffer+i);
+      if (count <= 0)
+        break;
+    }
+    if (i < (ssize_t) length)
+      break;
+    length=fread(buffer,1,quantum,file);
+  }
+  if (ferror(file) != 0)
+    status=MagickFalse;
+  (void) fclose(file);
+  buffer=(char *) RelinquishMagickMemory(buffer);
+  if (CloseBlob(image) == MagickFalse)
+    status=MagickFalse;
+  (void) RelinquishUniqueFileResource(write_info->unique);
+  write_info=DestroyImageInfo(write_info);
+  return(status);
 }
diff --git a/config/delegates.xml.in b/config/delegates.xml.in
index fa84a7cb5..8f7a1ae26 100644
--- a/config/delegates.xml.in
+++ b/config/delegates.xml.in
@@ -63,7 +63,7 @@
   <delegate decode="png" encode="bpg" command="@SHELL_QUOTE@@BPGEncodeDelegate@@SHELL_QUOTE@ -b 12 -q @SHELL_QUOTE@%~@SHELL_QUOTE@ -o @SHELL_QUOTE@%o@SHELL_QUOTE@ @SHELL_QUOTE@%i@SHELL_QUOTE@"/>
   <delegate decode="blender" command="@SHELL_QUOTE@@BlenderDecodeDelegate@@SHELL_QUOTE@ -b @SHELL_QUOTE@%i@SHELL_QUOTE@ -F PNG -o @SHELL_QUOTE@%o@SHELL_QUOTE@@SHELL_QUOTE@\n@SHELL_QUOTE@@ConvertDelegate@@SHELL_QUOTE@ -concatenate @SHELL_QUOTE@%o*.png@SHELL_QUOTE@ @SHELL_QUOTE@%o@SHELL_QUOTE@"/>
   <delegate decode="c2pa:decode" command="@SHELL_QUOTE@@C2PADecodeDelegate@@SHELL_QUOTE@ @SHELL_QUOTE@%i@SHELL_QUOTE@ --output @SHELL_QUOTE@%o@SHELL_QUOTE@"/>
-  <delegate encode="c2pa:encode" command="env C2PA_PRIVATE_KEY=%[c2pa:key] C2PA_PASSWORD=%[c2pa:password] C2PA_CERT_CHAIN=%[c2pa:cert] @SHELL_QUOTE@@C2PAEncodeDelegate@@SHELL_QUOTE@ @SHELL_QUOTE@%i@SHELL_QUOTE@ --manifest @SHELL_QUOTE@%[c2pa:manifest]@SHELL_QUOTE@ --output @SHELL_QUOTE@%o@SHELL_QUOTE@"/>
+  <delegate encode="c2pa:encode" command="env C2PA_PRIVATE_KEY=%[c2pa:key] C2PA_PASSWORD=%[c2pa:password] C2PA_CERT_CHAIN=%[c2pa:cert] @SHELL_QUOTE@@C2PAEncodeDelegate@@SHELL_QUOTE@ @SHELL_QUOTE@%i@SHELL_QUOTE@ --manifest @SHELL_QUOTE@%[c2pa:manifest]@SHELL_QUOTE@ --force --output @SHELL_QUOTE@%o@SHELL_QUOTE@"/>
   <delegate decode="browse" stealth="True" spawn="True" command="@SHELL_QUOTE@@BrowseDelegate@@SHELL_QUOTE@ https://imagemagick.org/; @RMDelegate@ @SHELL_QUOTE@%i@SHELL_QUOTE@"/>
   <delegate decode="http:decode" command="@SHELL_QUOTE@@WWWDecodeDelegate@@SHELL_QUOTE@ -s -L -o @SHELL_QUOTE@%u.dat@SHELL_QUOTE@ @SHELL_QUOTE@http:%M@SHELL_QUOTE@"/>
   <delegate decode="https:decode" command="@SHELL_QUOTE@@WWWDecodeDelegate@@SHELL_QUOTE@ -s -L -o @SHELL_QUOTE@%u.dat@SHELL_QUOTE@ @SHELL_QUOTE@https:%M@SHELL_QUOTE@"/>