如何使用PHP ImageMagic / Imagick从JSON /数据数组(宽度,高度,x,y,角度)创建大型(高质量300dpi)图像。

26
我使用 FabricJs 在Canvas上创建了一个设计(270x470),其中包含一些图片和文本,然后通过fabricJS的canvas.toJSON()方法将所有图片/文本信息以JSON格式导出。现在我需要在PHP中使用Imagick重新绘制该设计,使其成为高质量的图像(2790x4560)。

FabricJS Design

上述设计的JSON dataArray包含所有对象的信息,如大小、位置、角度等。
{
"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"
}}

这里是我关于使用JSON数组在PHP中生成高质量图像的代码片段
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();
    }
}

我的输出结果(90%)已经在上面的解决方案中了,但是我们可以看到一些图像(蓝色数字线)没有放在应该看起来像第一个设计图的确切位置。

Imagick Design

基本上我想做的是,在循环内调用addImage方法对每个图像进行缩放-旋转-定位,以在Print Image(300DPi)上呈现。我不确定在Imagick中旋转图像后如何获得精确偏移量(新的x、y坐标/位置/左上角),或者在缩放后再合成时是否旋转对象。也许有一个数学公式像Math.PI :) 问题是:如何根据旋转角度/角度计算新的偏移/位置?希望发布的片段对所有人都有用。

1
将问题编辑为与给定答案不符是不礼貌的,至少如此。不断在同一个问题上提供赏金是你自己的事情,我想(或者也许不是)。你有4个代码转储和2个图像,但很少解释我们正在看什么。你问了4个问题。(1)一旦你澄清了你正在做什么,以便我们可以弄清楚,它就会成为话题。(2)可能会成为话题。(3)不是话题。(4)不是话题。 - Teepeemm
谢谢,现在我的问题清楚了。"Offset"。 - AZinkey
对于这个设计,你是从这个开始的吗?最终想要得到什么?我猜"旋转度/角度"是数字行的角度?偏移量是指相对于哪里的偏移?offset在你的代码中没有出现,所以你想用它做什么?唯一明显的事情是你的图片是不同的。 - Teepeemm
编号行只是一个图像,例如“src”:“http:\\example.com/images/098f20be9fb7b66d00cb573acc771e99.JPG”“对于此设计”,包含1个灰色圆形/度数图像和4个带有粉红背景的蓝线数字图像我使用这些类型的图像来确保它们在画布设计中的位置和角度完全一致。 - AZinkey
非常感谢,它像魔法一样正常运行 :) - Poornima Subramani Naidu
3个回答

9

这不是完整的答案,但你正在完全错误的方向上解决问题。

Fabric.js 已经有一种方法可以使用 canvas.toSVG() 函数将画布保存为 SVG 格式。Imagick 可以打开 SVG 文件并按您需要的任何质量将其转换为 PNG。

当尝试包含在图像中的位图时会存在问题,例如:

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

我强烈建议您自己在服务器上下载这些文件,而不是让Imagick来下载它们。这不仅会让您更好地控制可能发生的任何错误,而且还可以限制一些安全风险。允许人们从您的服务器中下载任意数据,然后由一个存在许多内存访问错误的库使用该数据,这并不是一个好主意。
通常的做法是,在fabric.js创建SVG之前将图像的src替换为对本地文件名的引用,或者在转换完成后更加非正式地进行替换 - 并在进行此替换时生成需要从远程服务器下载的文件列表。
实际的实现细节留给OP作为练习。
顺便说一句,有很大的机会已经有人做过这个了... 您是否彻底搜索了packagist/github?

3
你应该将这个问题作为一个独立的问题发布在“为什么Imagick无法转换由Fabric生成的SVG”下。 - Danack
Imagick的构建版本可能是无法将fabricJS的SVG转换为PNG的原因,但我们可以通过exec(convert design.svg output.png)来实现。顺便说一句,我想通过DataArray来实现它,而且重要的是我的问题是关于“计算旋转偏移量”,不是吗? - AZinkey
1
不,你的问题是“如何从数据数组创建高质量图像”,而答案是使用SVG。如果你有关于Imagick中旋转的具体问题,你应该提出一个独立的简单问题,并标记为Imagick。 - Danack
对不起,我的英语很差,这个问题应该取什么好标题? - AZinkey
我希望现在清楚了,我正在寻找的是“缩放旋转图像后的X、Y - 左、上位置”。 - AZinkey
显示剩余2条评论

0

这里我得到了一个解决方案,也许它会像对我一样帮助其他人

<?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: https://dev59.com/y1bTa4cB1Zd3GeqP-3M6
            $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;
}

太棒了!你的解决方案帮助了我们很多 :) (心形眼睛) - Poornima Subramani Naidu
以上代码存在错误,应该改为以下内容:foreach ($imageObjects as $object) { addImageToLarge($object, $print, $printData, $newPrintData); addTextToLarge($object, $print, $printData, $newPrintData); } - mrcoder

0

我想我明白你在寻找什么,我会给你一个函数,它是几年前用来从JSON字符串生成高质量图像的。不过你需要进行必要的更改。我的输出扩展名是tiff。而且json字符串是使用1%缩小的png版本制作的,后来我将s、y值缩放以获取100%全尺寸的psd文件。

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;
}

请随时提出任何疑问。 - Vinu vasudev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接