26

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.

FabricJS Design

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

Imagick Design

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.

Cœur
  • 37,241
  • 25
  • 195
  • 267
AZinkey
  • 5,209
  • 5
  • 28
  • 46
  • 1
    Editing the question away from the given answer is impolite, at best. Continually offering a bounty on the same question is your own business, I suppose (or maybe not). You have 4 code dumps and 2 images, but very little explaining what we're looking at. You ask 4 questions. (1) will be on topic once you clarify what you're doing so that we can figure it out. (2) will probably be on topic. (3) Not on topic. (4) Not on topic. – Teepeemm Jun 22 '15 at 20:27
  • Thanks man, is now my question is clear.. Offset" – AZinkey Jun 23 '15 at 17:52
  • "For this design" Is this what you started with? And want to end up with? I assume "Rotation Degree/Angle" is the angle of the line of numbers? What is the offset? Specifically, offset from where? `offset` doesn't appear in your code, so what do you want to do with it? The only thing I find obvious is that your pictures are different. – Teepeemm Jun 23 '15 at 20:10
  • line of numbers is just an image like "src": "http:\\example.com/images/098f20be9fb7b66d00cb573acc771e99.JPG" "For this design" contains 1 gray circle/degree image and 4 blue line numbers image with pink background i used this types of image to be sure that they are position at exact same location/position and angle as they are in canvas design – AZinkey Jun 25 '15 at 19:07
  • Thanks a ton, it worked like a charm :) – Poornima Subramani Naidu Aug 23 '18 at 10:26

3 Answers3

9

This is not a complete answer, but you are going about this completely wrong.

Fabric.js already has a way of saving a canvas to SVG format with the canvas.toSVG() function. Imagick can open SVG files and convert them to PNG at any quality you want.

There will be an issue when trying to include the bitmaps that are included in the image e.g.

"src": "http:\\example.com/images/3f0bc771261860d917e0ad6d09cb2064.png",

I would strongly recommend downloading these yourself on the server, rather than allowing Imagick to download them. Not only will that give you better control over any errors that may occur, but also limits some security risks. Allowing people to download arbitrary data from within your server and then having that data used by a library that has had many bugs with memory access is not a good idea.

The general way to do this would be to replace the src of the image with a reference to a local file name either before fabric.js creates the SVG or you could do it even more hackily after it's been converted - and when you do this replacement generate a list of files that need to be downloaded from a remote server.

The actual implementation details are left as an exercise for OP.

btw there is a reasonable chance someone has already done this....have you searched packagist/github thoroughly?

Danack
  • 24,939
  • 16
  • 90
  • 122
  • and i also tried toSVG method previous, which contains more issues (not only image data encoding ) even this simple text not converting correctly by Imagick Suraj – AZinkey Mar 07 '15 at 12:50
  • 3
    You should post that as a separate questions under "Why does Imagick not convert this SVG generated by Fabric". – Danack Mar 07 '15 at 13:05
  • Imagick build version can be cause of reason for not convert fabricJS's SVG to png, but we could achieve it through exec( convert design.svg output.png ) BTW i want it through DataArray, And important thing my question is about "Calculate Offset on Rotation", isn't it..? – AZinkey Jun 22 '15 at 19:53
  • 1
    No your question is "How to create a high quality image from array of data" and the answer to that is to use SVG. If you have a specific question about rotations in Imagick, you should ask that a standalone simple question, tagged with Imagick. – Danack Jun 22 '15 at 20:11
  • I am sorry for my poor English,What should be a good Title for this question – AZinkey Jun 23 '15 at 17:56
  • @AZinkey Isolate a single problem and ask that as a new question. Your English is fine - the issue is that the question here is an essay that contains lot of different things. – Danack Jun 23 '15 at 21:01
  • i hope it is clear now that what i am looking for is "X, Y - Left, Top position after scale-Rotate an image – AZinkey Jun 25 '15 at 19:01
0

Think I got what you are looking for, will let you have a function which used few years back to generate a high quality image from a JSON string. You have to make necessary changes though. My output extension is tiff. And json string is made using 1% resized png versions later I scale the s, y values to take the 100% full sized psd's.

function generateDesignImage($arr_params,$arr_design){
    extract($arr_params);
    $images     = $arr_design['sprites'];
    $dir        = $arr_design['options']['source_dir'];
    $ext    = $arr_design['options']['in_ext'];
    $side   = $arr_design['options']['img_side'];
    $out_ext = $arr_design['options']['out_ext'];

    // Canvas   
    $im = new Imagick();
    $im->newImage(6000,6000,'transparent');
    $im->setImageColorSpace(Imagick::COLORSPACE_CMYK);
    //$im->setImageDepth(8);
    /********************* Code for image arrangements *************************/
    $i      =0;
    $min_X  = $min_Y    = 6000;
    $max_X  = $max_Y    = 0;
    $scale  = 10;
    foreach($images as $sprites=>$val){
        $var        =   "img_$i";
        $var    = new Imagick();
        $var->resizeImage($var->getImageWidth()/$scale,$var->getImageHeight()/$scale,Imagick::FILTER_LANCZOS,1,TRUE);
        /************ Find out x,y,width and height *********************/
        $c_width    = $var->getImageWidth()/2;
        $c_height   = $var->getImageHeight()/2;
        $x1     = ($val['x']*$scale/$val['scale'])-$c_width;
        $y1         = ($val['y']*$scale/$val['scale'])-$c_height;
        $x2     = ($val['x']*$scale/$val['scale'])+$c_width;
        $y2     = ($val['y']*$scale/$val['scale'])+$c_height;
        $min_X  = ($min_X >= $x1)?$x1:$min_X;
        $min_Y  = ($min_Y >= $y1)?$y1:$min_Y;
        $max_X  = ($max_X <= $x2)?$x2:$max_X;
        $max_Y  = ($max_Y <= $y2)?$y2:$max_Y;
        /***************************************************************/

        $im->compositeImage($var, $var->getImageCompose(), $x1,$y1,imagick::MONTAGEMODE_FRAME);
        $i++;
    }
    /**************************************************************************/    
        $im->setImageFormat( $out_ext );    
        /******************Crop to Exact Fit ********************************/
    $im->cropImage ( $max_X-$min_X+100,$max_Y-$min_Y+100  ,$min_X-50  ,$min_Y-50 );
        /************************************************************************/
    $success1 = $im->writeImage( 'Out_image_'.$design_id.'.'.$out_ext);
    $var->BorderImage(new ImagickPixel("white") , 5,5);
    return $success1;
}
Ninjakannon
  • 3,751
  • 7
  • 53
  • 76
Vinu vasudev
  • 109
  • 2
  • 8
0

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;
}
AZinkey
  • 5,209
  • 5
  • 28
  • 46
  • Awesome ! your solution helped us in a great way :) (Hearteyes) – Poornima Subramani Naidu Aug 23 '18 at 10:24
  • there is error in above code here`foreach ($imageObjects as $object) { addImageToLarge($object, $print, $printData, $newPrintData); } foreach ($imageObjects as $object) { addTextToLarge($object, $print, $printData, $newPrintData); }` – mrcoder Oct 01 '18 at 07:25