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@"/>