[MagickWand] Keep SVG background transparency when compositing

Questions and postings pertaining to the development of ImageMagick, feature enhancements, and ImageMagick internals. ImageMagick source code and algorithms are discussed here. Usage questions which are too arcane for the normal user list should also be posted here.
Post Reply
someone
Posts: 3
Joined: 2018-07-18T04:00:29-07:00
Authentication code: 1152

[MagickWand] Keep SVG background transparency when compositing

Post by someone »

Hi everyone,

I'm using the C API like to do some picture watermarking but I can't get it working.
Basically, I want to do the following command programmatically :

Code: Select all

composite -background none -dissolve 25% -gravity center Vector-based_exemple.svg IMG_1780.JPG out.jpg
The SVG file is taken from wikimedia and the JPG file is a picture I took with my camera.

My code works except:
- I have to manually compute the watermark offset. I tried MagickSetGravity but it seems it doesn't do anything in my case.
- I'm loosing the transparent background. I tried PixelSetColor + MagickSetImageBackgroundColor, played with MagickSetImageAlpha, checked the colorspace is sRGB.. with no success. If I manually convert the SVG file to a PNG one using GIMP and update my code to point to the right file it works so I guess it may be related to the SVG parser.

My code is a bit long but it's very straightforward.

Code: Select all

#include <stdio.h> /* For printf */
#include <stdbool.h> /* For bool */
#include <stdint.h> /* For uint8_t */
#include <MagickWand/MagickWand.h>

bool loadPictureIntoBuffer(char *pictureFilePath,
    uint8_t **pictureBuffer, size_t *pictureBufferLen)
{
    size_t nbRead;
    FILE *pictureFile;
    size_t pictureFileLen;
    uint8_t *pictureBufferTemp;

    if (!pictureFilePath) return false;
    if (!pictureBuffer) return false;
    if (!pictureBufferLen) return false;

    pictureFile = NULL;
    pictureBufferTemp = NULL;

    pictureFile = fopen(pictureFilePath, "rb");
    if (!pictureFile) goto cleanup;

    fseek(pictureFile, 0, SEEK_END);
    pictureFileLen = ftell(pictureFile) * sizeof(uint8_t);
    if (!pictureFileLen) goto cleanup;
    fseek(pictureFile, 0, SEEK_SET);

    pictureBufferTemp = malloc(pictureFileLen);
    if (!pictureBufferLen) goto cleanup;

    nbRead = fread(pictureBufferTemp, 1, pictureFileLen, pictureFile);
    if (nbRead != pictureFileLen) goto cleanup;

    fclose(pictureFile);
    *pictureBuffer = pictureBufferTemp;
    *pictureBufferLen = pictureFileLen;
    return true;

cleanup:
    if (pictureBufferTemp) free(pictureBufferTemp);
    if (pictureFile) fclose(pictureFile);
    return false;
}

bool readPictureFromMemory(MagickWand *magickWand,
    uint8_t *pictureBuffer, size_t pictureBufferLen,
    size_t *pictureWidth, size_t *pictureHeight)
{
    PixelWand *pxWand;
    MagickBooleanType ret;
    size_t pictureWidthTemp;
    size_t pictureHeightTemp;
    OrientationType pictureOrientation;

    if (!magickWand) return false;
    if (!pictureBuffer) return false;
    if (!pictureBufferLen) return false;
    if (!pictureWidth) return false;
    if (!pictureHeight) return false;

    pxWand = NULL;

    ret = MagickReadImageBlob(magickWand, pictureBuffer, pictureBufferLen);
    if (!ret) return false;

    pictureOrientation = MagickGetImageOrientation(magickWand);
    switch (pictureOrientation)
    {
        case LeftTopOrientation:
        case TopRightOrientation:
        case RightBottomOrientation:
        case LeftBottomOrientation:
        {
            pictureWidthTemp = MagickGetImageHeight(magickWand);
            pictureHeightTemp = MagickGetImageWidth(magickWand);
        }
        default:
        {
            pictureWidthTemp = MagickGetImageWidth(magickWand);
            pictureHeightTemp = MagickGetImageHeight(magickWand);
        }
    }
    if (!pictureWidthTemp || !pictureHeightTemp) return false;

    switch (pictureOrientation)
    {
        case UndefinedOrientation:
        case TopLeftOrientation:
        default:
            break;
        case TopRightOrientation:
        {
            ret = MagickFlopImage(magickWand);
            if (!ret) return false;
        }
        case BottomRightOrientation:
        {
            pxWand = NewPixelWand();
            if (!pxWand) goto cleanup;

            ret = MagickRotateImage(magickWand, pxWand, 180.0);
            if (!ret) goto cleanup;
        }
        case BottomLeftOrientation:
        {
            ret = MagickFlipImage(magickWand);
            if (!ret) return false;
        }
        case LeftTopOrientation:
        {
            ret = MagickTransposeImage(magickWand);
            if (!ret) return false;
        }
        case RightTopOrientation:
        {
            pxWand = NewPixelWand();
            if (!pxWand) goto cleanup;

            ret = MagickRotateImage(magickWand, pxWand, 90.0);
            if (!ret) goto cleanup;
        }
        case RightBottomOrientation:
        {
            ret = MagickTransverseImage(magickWand);
            if (!ret) return false;
        }
        case LeftBottomOrientation:
        {
            pxWand = NewPixelWand();
            if (!pxWand) goto cleanup;

            ret = MagickRotateImage(magickWand, pxWand, 270.0);
            if (!ret) goto cleanup;
        }
    }

    ret = MagickSetImageOrientation(magickWand, TopLeftOrientation);
    if (!ret) goto cleanup;

    *pictureWidth = pictureWidthTemp;
    *pictureHeight = pictureHeightTemp;
    return true;

cleanup:
    if (pxWand) DestroyPixelWand(pxWand);
    return false;
}

int main(int argc, char **argv)
{
    bool bret;
    size_t pictureWidth;
    size_t pictureHeight;
    MagickBooleanType ret;
    uint8_t *pictureBuffer;
    size_t pictureBufferLen;
    ssize_t watermarkOffsetX;
    ssize_t watermarkOffsetY;
    MagickWand *mainMagickWand;
    size_t watermarkPictureWidth;
    size_t watermarkPictureHeight;
    MagickWand *watermarkMagickWand;
    uint8_t *watermarkPictureBuffer;
    size_t watermarkPictureBufferLen;    

    pictureBuffer = NULL;
    pictureBufferLen = 0;
    mainMagickWand = NULL;
    watermarkMagickWand = NULL;
    watermarkPictureBuffer = NULL;
    watermarkPictureBufferLen = 0;

    MagickWandGenesis();

    mainMagickWand = NewMagickWand();
    if (!mainMagickWand) goto cleanup;

    watermarkMagickWand = NewMagickWand();
    if (!watermarkMagickWand) goto cleanup;


    bret = loadPictureIntoBuffer("IMG_1780.JPG",
        &pictureBuffer, &pictureBufferLen);
    if (!bret) goto cleanup;

    bret = readPictureFromMemory(mainMagickWand,
        pictureBuffer, pictureBufferLen,
        &pictureWidth, &pictureHeight);
    if (!bret) goto cleanup;


    bret = loadPictureIntoBuffer("Vector-based_example.svg",
        &watermarkPictureBuffer, &watermarkPictureBufferLen);
    if (!bret) goto cleanup;

    bret = readPictureFromMemory(watermarkMagickWand,
        watermarkPictureBuffer, watermarkPictureBufferLen,
        &watermarkPictureWidth, &watermarkPictureHeight);
    if (!bret) goto cleanup;


    ret = MagickEvaluateImage(watermarkMagickWand, MultiplyEvaluateOperator, 0.5);
    if (!ret) goto cleanup;

    if (pictureWidth < watermarkPictureWidth) goto cleanup;
    if (pictureHeight < watermarkPictureHeight) goto cleanup;
    watermarkOffsetX = (pictureWidth - watermarkPictureWidth)/2;
    watermarkOffsetY = (pictureHeight - watermarkPictureHeight)/2;

    ret = MagickCompositeImage(mainMagickWand,
        watermarkMagickWand, OverCompositeOp,
        MagickTrue, watermarkOffsetX, watermarkOffsetY);
    if (!ret) goto cleanup;


    ret = MagickWriteImage(mainMagickWand, "out.jpg");
    if (!ret) goto cleanup;


    printf("done\n");

    if (watermarkPictureBuffer) free(watermarkPictureBuffer);
    if (pictureBuffer) free(pictureBuffer);
    DestroyMagickWand(watermarkMagickWand);
    DestroyMagickWand(mainMagickWand);

    MagickWandTerminus();
    return 0;

cleanup:
    printf("error\n");

    if (watermarkPictureBuffer) free(watermarkPictureBuffer);
    if (pictureBuffer) free(pictureBuffer);
    DestroyMagickWand(watermarkMagickWand);
    DestroyMagickWand(mainMagickWand);

    MagickWandTerminus();
    return -1;
}
Any idea ?
Thanks for reading me!
User avatar
fmw42
Posts: 25562
Joined: 2007-07-02T17:14:51-07:00
Authentication code: 1152
Location: Sunnyvale, California, USA

Re: [MagickWand] Keep SVG background transparency when compositing

Post by fmw42 »

First I would suggest you use the convert syntax in the command line rather than the composite syntax. The former is more flexible.

But the main question is whether your SVG and/or JPG files are RGB or CMYK. ImageMagick does not always work well to composite if one or both images are CMYK. So if so, convert them to sRGB before compositing.

If you can post both images to some free hosting service and put the URLs here, we can test further in the command line to verify that works properly.

Please always provide your IM version and platform when asking questions.
snibgo
Posts: 12159
Joined: 2010-01-23T23:01:33-07:00
Authentication code: 1151
Location: England, UK

Re: [MagickWand] Keep SVG background transparency when compositing

Post by snibgo »

"-background None" tells the SVG rasterizer to make the background transparent, instead of white or whatever. So it needs to be set before rasterizing the SVG, which I suppose happens in MagickReadImageBlob().
snibgo's IM pages: im.snibgo.com
someone
Posts: 3
Joined: 2018-07-18T04:00:29-07:00
Authentication code: 1152

Re: [MagickWand] Keep SVG background transparency when compositing

Post by someone »

I'm quite impressed for you quick answers, thanks !

fmw42 wrote: 2018-07-18T11:06:17-07:00 First I would suggest you use the convert syntax in the command line rather than the composite syntax. The former is more flexible.
Actually, my watermarking application is a CGI script so I receive picture data before writting them on disk.
I can't specify memory pointer using MagickCommandGenesis but yeah, it's a viable option.
And I'm glad it's exported because ffmpeg does not and you have to read source code to understand what functions to call to replicate the command-line you're interested in.

fmw42 wrote: 2018-07-18T11:06:17-07:00 But the main question is whether your SVG and/or JPG files are RGB or CMYK. ImageMagick does not always work well to composite if one or both images are CMYK. So if so, convert them to sRGB before compositing.
My source files are both sRGB but I will keep that in mind, thanks.

fmw42 wrote: 2018-07-18T11:06:17-07:00 If you can post both images to some free hosting service and put the URLs here, we can test further in the command line to verify that works properly.
In fact, any (sRGB) JPG will trigger the behavior. I tested with the first JPEG I found on the Internet and got the same result.

fmw42 wrote: 2018-07-18T11:06:17-07:00 Please always provide your IM version and platform when asking questions.
I'm using ImageMagick 7.0.8-3-Q16 2018-06-25 on Windows 7 but I also tested yesterday git HEAD on Linux and got the same result.

snibgo wrote: 2018-07-18T11:44:12-07:00 "-background None" tells the SVG rasterizer to make the background transparent, instead of white or whatever. So it needs to be set before rasterizing the SVG, which I suppose happens in MagickReadImageBlob().
I tested many combinations and none worked. but just in case, I implemented your suggestion (creating a pixel wand, setting its color to none and associating the pixel wand to the watermark wand before loading the picture) and it works!

For reference, I'm giving the full code so others can use it:

Code: Select all

#include <stdio.h> /* For printf */
#include <stdbool.h> /* For bool */
#include <stdint.h> /* For uint8_t */
#include <MagickWand/MagickWand.h>

bool loadPictureIntoBuffer(char *pictureFilePath,
    uint8_t **pictureBuffer, size_t *pictureBufferLen)
{
    size_t nbRead;
    FILE *pictureFile;
    size_t pictureFileLen;
    uint8_t *pictureBufferTemp;

    if (!pictureFilePath) return false;
    if (!pictureBuffer) return false;
    if (!pictureBufferLen) return false;

    pictureFile = NULL;
    pictureBufferTemp = NULL;

    pictureFile = fopen(pictureFilePath, "rb");
    if (!pictureFile) goto cleanup;

    fseek(pictureFile, 0, SEEK_END);
    pictureFileLen = ftell(pictureFile) * sizeof(uint8_t);
    if (!pictureFileLen) goto cleanup;
    fseek(pictureFile, 0, SEEK_SET);

    pictureBufferTemp = malloc(pictureFileLen);
    if (!pictureBufferLen) goto cleanup;

    nbRead = fread(pictureBufferTemp, 1, pictureFileLen, pictureFile);
    if (nbRead != pictureFileLen) goto cleanup;

    fclose(pictureFile);
    *pictureBuffer = pictureBufferTemp;
    *pictureBufferLen = pictureFileLen;
    return true;

cleanup:
    if (pictureBufferTemp) free(pictureBufferTemp);
    if (pictureFile) fclose(pictureFile);
    return false;
}

bool readPictureFromMemory(MagickWand *magickWand,
    uint8_t *pictureBuffer, size_t pictureBufferLen,
    size_t *pictureWidth, size_t *pictureHeight)
{
    PixelWand *pxWand;
    MagickBooleanType ret;
    size_t pictureWidthTemp;
    size_t pictureHeightTemp;
    OrientationType pictureOrientation;

    if (!magickWand) return false;
    if (!pictureBuffer) return false;
    if (!pictureBufferLen) return false;
    if (!pictureWidth) return false;
    if (!pictureHeight) return false;

    pxWand = NULL;

    ret = MagickReadImageBlob(magickWand, pictureBuffer, pictureBufferLen);
    if (!ret) return false;

    pictureOrientation = MagickGetImageOrientation(magickWand);
    switch (pictureOrientation)
    {
        case LeftTopOrientation:
        case TopRightOrientation:
        case RightBottomOrientation:
        case LeftBottomOrientation:
        {
            pictureWidthTemp = MagickGetImageHeight(magickWand);
            pictureHeightTemp = MagickGetImageWidth(magickWand);
        }
        default:
        {
            pictureWidthTemp = MagickGetImageWidth(magickWand);
            pictureHeightTemp = MagickGetImageHeight(magickWand);
        }
    }
    if (!pictureWidthTemp || !pictureHeightTemp) return false;

    switch (pictureOrientation)
    {
        case UndefinedOrientation:
        case TopLeftOrientation:
        default:
            break;
        case TopRightOrientation:
        {
            ret = MagickFlopImage(magickWand);
            if (!ret) return false;
        }
        case BottomRightOrientation:
        {
            pxWand = NewPixelWand();
            if (!pxWand) goto cleanup;

            ret = MagickRotateImage(magickWand, pxWand, 180.0);
            if (!ret) goto cleanup;
        }
        case BottomLeftOrientation:
        {
            ret = MagickFlipImage(magickWand);
            if (!ret) return false;
        }
        case LeftTopOrientation:
        {
            ret = MagickTransposeImage(magickWand);
            if (!ret) return false;
        }
        case RightTopOrientation:
        {
            pxWand = NewPixelWand();
            if (!pxWand) goto cleanup;

            ret = MagickRotateImage(magickWand, pxWand, 90.0);
            if (!ret) goto cleanup;
        }
        case RightBottomOrientation:
        {
            ret = MagickTransverseImage(magickWand);
            if (!ret) return false;
        }
        case LeftBottomOrientation:
        {
            pxWand = NewPixelWand();
            if (!pxWand) goto cleanup;

            ret = MagickRotateImage(magickWand, pxWand, 270.0);
            if (!ret) goto cleanup;
        }
    }

    ret = MagickSetImageOrientation(magickWand, TopLeftOrientation);
    if (!ret) goto cleanup;

    *pictureWidth = pictureWidthTemp;
    *pictureHeight = pictureHeightTemp;
    return true;

cleanup:
    if (pxWand) DestroyPixelWand(pxWand);
    return false;
}

int main(int argc, char **argv)
{
    bool bret;
    size_t pictureWidth;
    size_t pictureHeight;
    MagickBooleanType ret;
    uint8_t *pictureBuffer;
    size_t pictureBufferLen;
    ssize_t watermarkOffsetX;
    ssize_t watermarkOffsetY;
    MagickWand *mainMagickWand;
    size_t watermarkPictureWidth;
    size_t watermarkPictureHeight;
    MagickWand *watermarkMagickWand;
    uint8_t *watermarkPictureBuffer;
    size_t watermarkPictureBufferLen;
    PixelWand *pxWand;

    pictureBuffer = NULL;
    pictureBufferLen = 0;
    mainMagickWand = NULL;
    watermarkMagickWand = NULL;
    watermarkPictureBuffer = NULL;
    watermarkPictureBufferLen = 0;
    pxWand = NULL;

    MagickWandGenesis();

    mainMagickWand = NewMagickWand();
    if (!mainMagickWand) goto cleanup;

    watermarkMagickWand = NewMagickWand();
    if (!watermarkMagickWand) goto cleanup;


    pxWand = NewPixelWand();
    if (!pxWand) goto cleanup;

    ret = PixelSetColor(pxWand, "none");
    if (!ret) goto cleanup;

    ret = MagickSetBackgroundColor(watermarkMagickWand, pxWand);
    if (!ret) goto cleanup;

    bret = loadPictureIntoBuffer("IMG_1780.JPG",
        &pictureBuffer, &pictureBufferLen);
    if (!bret) goto cleanup;

    bret = readPictureFromMemory(mainMagickWand,
        pictureBuffer, pictureBufferLen,
        &pictureWidth, &pictureHeight);
    if (!bret) goto cleanup;


    bret = loadPictureIntoBuffer("Vector-based_example.svg",
        &watermarkPictureBuffer, &watermarkPictureBufferLen);
    if (!bret) goto cleanup;

    bret = readPictureFromMemory(watermarkMagickWand,
        watermarkPictureBuffer, watermarkPictureBufferLen,
        &watermarkPictureWidth, &watermarkPictureHeight);
    if (!bret) goto cleanup;


    ret = MagickEvaluateImage(watermarkMagickWand, MultiplyEvaluateOperator, 0.75);
    if (!ret) goto cleanup;

    ret = MagickCompositeImageGravity(mainMagickWand,
        watermarkMagickWand, OverlayCompositeOp, CenterGravity);
    if (!ret) goto cleanup;

    ret = MagickWriteImage(mainMagickWand, "out.jpg");
    if (!ret) goto cleanup;


    printf("done\n");

    if (pxWand) DestroyPixelWand(pxWand);
    if (watermarkPictureBuffer) free(watermarkPictureBuffer);
    if (pictureBuffer) free(pictureBuffer);
    DestroyMagickWand(watermarkMagickWand);
    DestroyMagickWand(mainMagickWand);

    MagickWandTerminus();
    return 0;

cleanup:
    printf("error\n");

    if (pxWand) DestroyPixelWand(pxWand);
    if (watermarkPictureBuffer) free(watermarkPictureBuffer);
    if (pictureBuffer) free(pictureBuffer);
    DestroyMagickWand(watermarkMagickWand);
    DestroyMagickWand(mainMagickWand);

    MagickWandTerminus();
    return -1;
}
I found MagickCompositeImageGravity with OverlayCompositeOp was giving better result.

Anyway, thanks for your help, I really appreciate it!
User avatar
fmw42
Posts: 25562
Joined: 2007-07-02T17:14:51-07:00
Authentication code: 1152
Location: Sunnyvale, California, USA

Re: [MagickWand] Keep SVG background transparency when compositing

Post by fmw42 »

composite -background none -dissolve 25% -gravity center Vector-based_exemple.svg IMG_1780.JPG out.jpg
JPG does not support transparency. So you need to save to PNG or TIFF to keep the transparency.
someone
Posts: 3
Joined: 2018-07-18T04:00:29-07:00
Authentication code: 1152

Re: [MagickWand] Keep SVG background transparency when compositing

Post by someone »

fmw42 wrote: 2018-07-19T09:38:44-07:00 JPG does not support transparency. So you need to save to PNG or TIFF to keep the transparency.
You don't seems to understand my (now solved) issue ? Did you actually test my (first) code ?

I'm taking an SVG picture with no (=transparent) background to watermark another picture (a JPG file).
I was getting the JPG content with the watermark dissolved in the center but the watermark background was white resulting in a big white square in the middle.
Setting the watermark wand to pixel colour none before loading the SVG picture did the trick.

If you don't see what I mean, compile my first code and use SVG and JPG file I provided.
Post Reply