How to create a large (High Quality 300dpi) image from JSON / array of Data (width,height,x,y,angels) using PHP ImageMagic / Imagick

imagemagick, imagick, math, php, rotation

Solution

Here I get a solution, may be it will help others like for me

<?php

// AZinkey
ini_set('memory_limit', '1024M'); // may be need increase memory size
ini_set('display_errors', 1); // enable error display
error_reporting(E_ALL); // show all type errors

$id = $_GET['id'];
$file = $id . ".json"; // json file e.g. 1234.json
$printData = json_decode(file_get_contents($file));

$mask = "mask.png"; // a image (4395x4395) which contains 2669x4395 black fill in center
$maskImg = new Imagick($mask);
$d = $maskImg->getImageGeometry();

$maskWidth = $d['width'];
$maskHeight = $d['height'];

// Then reduce any list of integer
$cd = array_reduce(array($maskWidth, 400), 'gcd');
$r1 = $maskWidth / $cd;
$r2 = 400 / $cd;

$newPrintData['r1'] = $r1;
$newPrintData['r2'] = $r2;


try {
    $print = new Imagick();
    $print->setResolution(300, 300);
    $background = (empty($printData->json_data->background)) ? 'transparent' : $printData->json_data->background;
    $print->newImage($maskWidth, $maskHeight, new ImagickPixel($background));

    $print->setImageMatte(TRUE);
    $print->setImageFormat('png32');
    $print->setImageUnits(imagick::RESOLUTION_PIXELSPERCENTIMETER);
} catch (Exception $e) {
    echo $e->getMessage();
}

// create two array for store text & images information separately 
$imageObjects = $textObjects = [];

foreach ($printData->json_data->objects as $object) {
    if ($object->type == 'image') {
        $imageObjects[] = $object;
    } else if ($object->type == 'text') {
        $imageObjects[] = $object;
    }
}
foreach ($imageObjects as $object) {
    addImageToLarge($object, $print, $printData, $newPrintData);
}

foreach ($imageObjects as $object) {
    addTextToLarge($object, $print, $printData, $newPrintData);
}
try {
    $print->setImageFormat('png');
    $saveFile = $id . "_print.json"; // save large image _print.png
    file_put_contents($saveFile, $print);
} catch (Exception $e) {
    echo $e->getMessage();
    exit();
}

function addImageToLarge($object, $print, $printData, $newPrintData) {
    try {
        $src = new Imagick($object->src);
        $size = $src->getImageGeometry();
        $resizeWidth = changeDpi(scale($object->width, $newPrintData['r1'], $newPrintData['r2']) * $object->scaleX);
        $resizeHeight = changeDpi(scale($object->height, $newPrintData['r1'], $newPrintData['r2']) * $object->scaleY);

        $src->resizeImage($resizeWidth, $resizeHeight, Imagick::FILTER_LANCZOS, 1);
        $sizeAfterResize = $src->getImageGeometry();

        $src->rotateImage(new ImagickPixel('none'), $object->angle);
        $sizeAfterRotate = $src->getImageGeometry();

        $left = $object->left < 0 ? -1 * abs(changeDpi(scale($object->left, $newPrintData['r1'], $newPrintData['r2']))) : changeDpi(scale($object->left, $newPrintData['r1'], $newPrintData['r2']));
        $top = $object->top < 0 ? -1 * abs(changeDpi(scale($object->top, $newPrintData['r1'], $newPrintData['r2']))) : changeDpi(scale($object->top, $newPrintData['r1'], $newPrintData['r2']));

        $print->compositeImage($src, Imagick::COMPOSITE_OVER, $left, $top);
    } catch (Exception $e) {
        echo $e->getMessage();
        exit();
    }
}

function addTextToLarge($object, $print, $printData, $newPrintData) {
    $fnt['Times New Roman'] = "font/times_6.ttf";
    $fnt['Arial'] = "font/arial_8.ttf";
    $fnt['Arial Black'] = "font/ariblk_8.ttf";
    $fnt['Comic Sans MS'] = "font/comic_5.ttf";
    $fnt['Courier New'] = "font/cour_5.ttf";
    $fnt['Georgia'] = "font/georgia_5.ttf";
    $fnt['Impact'] = "font/impact_7.ttf";
    $fnt['Lucida Console'] = "font/lucon_3.ttf";
    $fnt['Lucida Sans Unicode'] = "font/l_4.ttf";
    $fnt['Palatino Linotype'] = "font/pala_7.ttf";
    $fnt['Tahoma'] = "font/tahoma_3.ttf";
    $fnt['Trebuchet MS'] = "font/trebuc_3.ttf";
    $fnt['Verdana'] = "font/verdana_5.ttf";

    try {
        $line_height_ratio = $object->lineHeight;
        $resizeWidth = changeDpi(scale($object->width, $newPrintData['r1'], $newPrintData['r2']) * $object->scaleX);
        $resizeHeight = changeDpi(scale($object->height, $newPrintData['r1'], $newPrintData['r2']) * $object->scaleY);

        $print2 = new Imagick();
        $print2->setResolution(300, 300);
        $print2->newImage($resizeWidth, $resizeHeight, "transparent");
        $print2->setImageVirtualPixelMethod(imagick::VIRTUALPIXELMETHOD_BACKGROUND);
        $print2->setImageFormat('png32');
        $print2->setImageUnits(imagick::RESOLUTION_PIXELSPERCENTIMETER);

        // Instantiate Imagick utility objects
        $draw = new ImagickDraw();
        $color = new ImagickPixel($object->fill);

        //$starting_font_size = 100*1.33;
        $font_size = (($object->fontSize * $resizeWidth) / $object->width);

        $draw->setFontWeight(($object->fontWeight == 'bold') ? 600 : 100 );
        $draw->setFontStyle(0);
        $draw->setFillColor($color);

        // Load Font 
        //$font_size = $starting_font_size;
        $draw->setFont($fnt[$object->fontFamily]);
        $draw->setFontSize($font_size);

        $draw->setTextAntialias(true);
        $draw->setGravity(Imagick::GRAVITY_CENTER);

        if ($object->stroke) {
            $draw->setStrokeColor($object->stroke);
            $draw->setStrokeWidth($object->strokeWidth);
            $draw->setStrokeAntialias(true);  //try with and without
        }

        $total_height = 0;

        // Run until we find a font size that doesn't exceed $max_height in pixels
        while (0 == $total_height || $total_height > $resizeHeight) {
            if ($total_height > 0) {
                $font_size--; // we're still over height, decrement font size and try again
            }
            $draw->setFontSize($font_size);

            // Calculate number of lines / line height
            // Props users Sarke / BMiner: http://stackoverflow.com/questions/5746537/how-can-i-wrap-text-using-imagick-in-php-so-that-it-is-drawn-as-multiline-text
            $words = preg_split('%\s%', $object->text, -1, PREG_SPLIT_NO_EMPTY);
            $lines = array();
            $i = 0;
            $line_height = 0;

            while (count($words) > 0) {
                $metrics = $print2->queryFontMetrics($draw, implode(' ', array_slice($words, 0, ++$i)));
                $line_height = max($metrics['textHeight'], $line_height);

                if ($metrics['textWidth'] > $resizeWidth || count($words) < $i) {
                    $lines[] = implode(' ', array_slice($words, 0, --$i));
                    $words = array_slice($words, $i);
                    $i = 0;
                }
            }

            $total_height = count($lines) * $line_height * $line_height_ratio;

            if ($total_height > 0) {

            }
        }
        // Writes text to image
        $x_pos = 0;
        $y_pos = 0;

        for ($i = 0; $i < count($lines); $i++) {
            $print2->annotateImage($draw, $x_pos, $y_pos + ($i * $line_height * $line_height_ratio), $object->angle, $lines[$i]);
        }

        if ($object->flipX == 1)
            $print2->flopImage(); // x
        if ($object->flipY == 1)
            $print2->flipImage(); // y

        $print2->trimImage(0);
        $print2->setImagePage(0, 0, 0, 0);

        $print2->resizeImage($resizeWidth, 0, Imagick::FILTER_CATROM, 0.9, false);

        $left = $object->left < 0 ? -1 * abs(changeDpi(scale($object->left, $newPrintData['r1'], $newPrintData['r2']))) : changeDpi(scale($object->left, $newPrintData['r1'], $newPrintData['r2']));
        $top = $object->top < 0 ? -1 * abs(changeDpi(scale($object->top, $newPrintData['r1'], $newPrintData['r2']))) : changeDpi(scale($object->top, $newPrintData['r1'], $newPrintData['r2']));

        $print->compositeImage($print2, Imagick::COMPOSITE_OVER, $left, $top);

        //header("Content-Type: image/png");
        //echo $print2;exit;
    } catch (Exception $e) {
        echo $e->getMessage();
        exit();
    }
}

//The greatest common divisor (GCD) 
function gcd($a, $b) {
    return $b ? gcd($b, $a % $b) : $a;
}

function changeDpi($px) {
    //return ($px/96)*300;
    return $px;
}

function scale($px, $r1, $r2) {
    return $px * $r1 / $r2;
}

Problem

I created a design (270x470) with some pictures and text on Canvas using FabricJs then i export all pictures/text information in JSON format by fabricJS's canvas.toJSON() method And Now i need to Re-Draw that design on a High Quality (2790x4560) image in PHP using Imagick. JSON dataArray for above design which contains all object's information like size,position,angle etc.. ``` { "width": "2790", "height": "4560", "json_data": { "objects": [{ "type": "image", "originX": "left", "originY": "top", "left": "5", "top": "105", "width": "260", "height": "260", "scaleX": "1", "scaleY": "1", "angle": "0", "opacity": "1", "src": "http:\\example.com/images/098f20be9fb7b66d00cb573acc771e99.JPG", }, { "type": "image", "originX": "left", "originY": "top", "left": "5", "top": "229.5", "width": "260", "height": "11", "scaleX": "1", "scaleY": "1", "angle": "0", "opacity": "1", "src": "http:\\example.com/images/aeced466089d875a7c0dc2467d179e58.png", }, { "type": "image", "originX": "left", "originY": "top", "left": "51.07", "top": "135.58", "width": "260", "height": "11", "scaleX": "1", "scaleY": "1", "angle": "47.41", "opacity": "1", "src": "http:\\example.com/images/910ce024d984b6419d708354bf3641a3.png", }, { "type": "image", "originX": "left", "originY": "top", "left": "139.71", "top": "104.97", "width": "260", "height": "11", "scaleX": "1", "scaleY": "1", "angle": "89.65", "opacity": "1", "src": "http:\\example.com/images/88e096a82e5f8a503a71233addaff64c.png", }, { "type": "image", "originX": "left", "originY": "top", "left": "230.78", "top": "146.93", "width": "260", "height": "11", "scaleX": "1", "scaleY": "1", "angle": "134.98", "src": "http:\\example.com/images/d2c0ec738c1fec827381cfeb600bd87d.png", }, { "type": "image", "originX": "left", "originY": "top", "left": "265.01", "top": "240.19", "width": "260", "height": "11", "scaleX": "1", "scaleY": "1", "angle": "179.86", "opacity": "1", "src": "http:\\example.com/images/3f0bc771261860d917e0ad6d09cb2064.png", }], "background": "#FF00FF" }} ``` And here my Code Snippet for generating High Quality Image in PHP using JSON dataArray ``` error_reporting(E_ALL | E_STRICT); try { $id = $_GET['id']; // Design ID define('DS', DIRECTORY_SEPARATOR); $jsonDir = dirname(__FILE__) . DS . 'media' . DS . 'designs'; $printData = json_decode(file_get_contents($jsonDir . DS . $id . '.json')); } catch (Exception $e) { echo $e->getMessage(); } try { $print = new Imagick(); $print->setResolution(300, 300); $background = (empty($printData->json_data->background)) ? 'transparent' : $printData->json_data->background; $print->newImage($printData->width, $printData->height, new ImagickPixel($background)); $print->setImageFormat('png32'); $print->setImageUnits(imagick::RESOLUTION_PIXELSPERCENTIMETER); } catch (Exception $e) { echo $e->getMessage(); } // Re-Scaling each Image/Text for Larger Canvas/Image foreach ($printData->json_data->objects as $i => $object) { if ($object->type == 'image') { addImage($object, $print, $printData); } else { addText($object, $print, $printData); } } try { // Saving High Quality Image in (300 dpi) $fileDir = dirname(__FILE__) . DS . 'media' . DS . 'prints'; if (!file_exists($fileDir) || !is_dir($fileDir)) { if (!mkdir($fileDir)) die("Could not create directory: {$fileDir}\n"); } $saved = $print->writeimage($fileDir . DS . $id . '.png'); header('Content-type: image/png'); echo $print; } catch (Exception $e) { echo $e->getMessage(); } ``` addImage(); ``` function addImage($object, $print, $printData) { try { $widthScale = ($printData->width / 270); $heightScale = ($printData->height / 470); $fileDir = dirname(__FILE__) . DS . 'media' . DS . 'original' . DS; $src = new Imagick($fileDir . basename($object->src)); $size = $src->getImageGeometry(); $resizeWidth = ($object->width * $object->scaleX) * $widthScale; $resizeHeight = ($object->height * $object->scaleY) * $heightScale; $src->resizeImage($resizeWidth, $resizeHeight, Imagick::FILTER_LANCZOS, 1); $sizeAfterResize = $src->getImageGeometry(); $src->rotateImage(new ImagickPixel('none'), $object->angle); $sizeAfterRotate = $src->getImageGeometry(); if (!$object->angle) { $left = $object->left * $widthScale; $top = $object->top * $heightScale; } else { switch ($object->angle) { case $object->angle > 315: $left = ($object->left * $widthScale); $top = ($object->top * $heightScale); break; case $object->angle > 270: $left = ($object->left * $widthScale); $top = ($object->top * $heightScale); break; case $object->angle > 225: $left = ($object->left * $widthScale); $top = ($object->top * $heightScale); break; case $object->angle > 180: $left = ($object->left * $widthScale); $top = ($object->top * $heightScale); break; case $object->angle > 135: $left = ($object->left * $widthScale); $top = ($object->top * $heightScale); break; case $object->angle > 90: $left = ($object->left * $heightScale) - ($sizeAfterRotate['width'] / 2); $top = ($object->top * $heightScale) - ($sizeAfterRotate['width'] / 2); break; case $object->angle > 45: $left = ($object->left * $widthScale) - $size['height'] * $widthScale; $top = ($object->top * $heightScale) - $size['height'] * $heightScale; break; default: $left = $object->left * $widthScale; $top = $object->top * $heightScale; break; } } $print->compositeImage($src, Imagick::COMPOSITE_DEFAULT, $left, $top); } catch (Exception $e) { echo $e->getMessage(); } } ``` My Output results (90%) is there with above solution, but as we can see some image (blue number line) doesn't place at exact position which should look like first design image Basically what i am trying to do is, " Inside a Loop calling an addImage Method for scale - rotate - position each image on Print Image(300DPi) i am not sure what i am missing to get exact offset (new x,y coordinates/position/Left-Top ) after Rotation for an image in Imagick or i am Rotating object after Scale then compose or May be A Math Formula like Math.PI :) Question is: How Can i Calculate New offset/Position according to Rotation Degree/Angle after Scale ? I hope posted snippet are useful for everyone.

Original source

Related problems