如何在没有抗锯齿的情况下将缩放文本作为图像

时间:2015-12-09 13:37:05

标签: javascript php html5 image canvas

我想将像素化文本(没有抗锯齿)放到HTML画布上。我考虑使用PHP使用imagettftext渲染文本,使用负颜色值(禁用消除锯齿),然后将该图像放到画布上。这工作正常,但我也想垂直或水平拉伸文本。有没有办法在PHP中执行此操作? (也可以使用其他脚本语言。)

背景: 虽然有方法可以在没有消除锯齿的情况下将图像绘制到HTML画布上,但我找不到任何文本内容。到目前为止,我已经使用了一个单独的画布,将我的文本放到它上面并通过设置所有alpha值来操纵imageData> 128到255,其他一切都为0.然后我将该图像复制到目标画布上。这里的问题是,特别是对于小字体大小,结果字符差异很大,字符的线条粗细不同。 Example output。 (示例被放大。查看" l"的不同厚度以及" o"的不同渲染。) 因此,任何想法如何在画布上获得清晰的非消除锯齿的文本或如何创建具有清晰和拉伸文本的图像都是非常受欢迎的。

1 个答案:

答案 0 :(得分:0)

我正在开发一个类似的项目,涉及使用PHP生成图像并将它们绘制到画布上。我注意到的一些事情是任何透明度都会影响图像的清晰度。如果您正在使用透明度,最好在画布上绘制一层白色的平面,然后再在其上绘制图像。这是我正在研究的课程,也许你可以从中获得一些参考。它也会像素化一样。

你可以像这样使用它:

$img = Image::createTextImage($text, $font_size, $color, $ttf_font_file);
$img->pixelate($blocksize);
$img->output('png');

这会将图像输出到可以加载到画布中的浏览器。

class Image{

    public $filepath;
    public $width;
    public $height;
    public $mime;
    public $landscape;
    public $imageFunct;
    public $compression;

    // Image resource identifier
    private $image;

    // Is it a temporary image
    private $isTemp;

    /**
     * constructor
     * @param type $fp
     * @param type $isTemp - if true, will delete the image file on the destroy call
     * @throws Exception
     */
    public function __construct($fp, $isTemp = false){
        // Make sure file exists
        if(!file_exists($fp)) throw new Exception("Image source file does not exist: $fp"); 

        $this->isTemp = $isTemp;
        $this->filepath = $fp;
        $data = getimagesize($fp);
        $this->width = $data[0];
        $this->height = $data[1];
        $this->landscape = $this->width > $this->height;
        $this->mime = $data['mime'];
        switch($this->mime){
            case("image/png"):
                $this->image = imagecreatefrompng($this->filepath);
                imagealphablending($this->image, false);
                imagesavealpha($this->image, true);
                $this->imageFunct = 'imagepng';
                $this->compression = 9;
                break;
            case('image/jpeg'):
            case('image/pjpeg'):
            case('image/x-jps'):
                $this->image = imagecreatefromjpeg($this->filepath);
                $this->imageFunct = 'imagejpeg';
                $this->compression = 100;
                break;
            case('image/gif'):
                $this->image = imagecreatefromgif($this->filepath);
                imagealphablending($this->image, false);
                imagesavealpha($this->image, true);
                $this->imageFunct = 'imagegif';
                break;
            default:
                throw new Exception("Invalid image type. Only excepts PNG, JPG, and GIF. You entered a {$this->mime} type image.");
        }
    }

    /**
     * scales the image to cover the dimensions provided
     * @param type $width (of canvas)
     * @param type $height (of canvas)
     */
    public function scale($width, $height, $cover='fill'){

        // Get new dimensions
        $imgRatio = $this->height/$this->width;
        $canvasRatio = $height/$width;
        if(
            ($canvasRatio > $imgRatio && $cover=='fill') || 
            ($canvasRatio <= $imgRatio && $cover!='fill')
        ){
            $finalHeight = $height;
            $scale = $finalHeight / $this->height;
            $finalWidth = $this->width * $scale;
        }else{
            $finalWidth = $width;
            $scale = $finalWidth / $this->width;
            $finalHeight = $this->height * $scale;
        }

        // Resize the image
        $thumb = imagecreatetruecolor($finalWidth, $finalHeight);
        imagealphablending($thumb, false);
        imagesavealpha($thumb, true);
        imagecopyresampled($thumb, $this->image, 0, 0, 0, 0, $finalWidth, $finalHeight, $this->width, $this->height);

        $this->resize($finalWidth, $finalHeight);
        $this->width = $finalWidth;
        $this->height = $finalHeight;
    }

    /**
     * scales the image to cover the dimensions provided
     * @param type $width (of canvas)
     * @param type $height (of canvas)
     */
    public function resize($width, $height){

        // Resize the image
        $thumb = imagecreatetruecolor($width, $height);
        imagealphablending($thumb, false);
        imagesavealpha($thumb, true);
        imagecopyresampled($thumb, $this->image, 0, 0, 0, 0, $width, $height, $this->width, $this->height);

        $this->image = $thumb;
        $this->width = $width;
        $this->height = $height;
    }

    /**
     * tile the image to the provided dimensions
     * @param type $width
     * @param type $height
     * @param type $output_mimetype
     */
    public function tile($width, $height){

        // Our output image to be created
        $out = imagecreatetruecolor($width, $this->height);
        imagealphablending($out, false);
        imagesavealpha($out, true);

        // Tile that shit horiz
        $curr_x = 0;
        while($curr_x < $width){
            imagecopy($out, $this->image, $curr_x, 0, 0, 0, $this->width, $this->height);
            $curr_x += $this->width;
        }

        // our final output image to be created
        $thumb = imagecreatetruecolor($width, $height);
        imagealphablending($thumb, false);
        imagesavealpha($thumb, true);

        // Tile that shit vert
        $curr_y = 0;
        while($curr_y < $height){
            imagecopy($thumb, $out, 0, $curr_y, 0, 0, $width, $this->height);
            $curr_y += $this->height;
        }

        imagedestroy($out);

        $this->image = $thumb;

    }

    /**
     * Reverse all colors of the image
     */
    public function reverseColors(){
        return imagefilter($this->image, IMG_FILTER_NEGATE);
    }

    /**
     * Convert image to greyscale
     */
    public function greyScale(){
        return imagefilter($this->image, IMG_FILTER_GRAYSCALE);
    }

    /**
     * Adjust brightness level. (between 255 and -255)
     * @param type $brightness
     */
    public function adjustBrightness($brightness){
        if($brightness > 255) $brightness = 255;
        if($brightness < -255) $brightness = -255;
        return imagefilter($this->image, IMG_FILTER_BRIGHTNESS, $brightness);
    }

    /**
     * Adjust the contrast level
     * @param int $contrast
     */
    public function adjustContrast($contrast){
        return imagefilter($this->image, IMG_FILTER_CONTRAST, $contrast);
    }

    /**
     * Turns on edgeDetect Filter
     */
    public function edgeDetect(){
        return imagefilter($this->image, IMG_FILTER_EDGEDETECT);
    }

    /**
     * Turns on emboss Filter
     */
    public function emboss(){
        return imagefilter($this->image, IMG_FILTER_EMBOSS);
    }

    /**
     * Turns on gaussianBlur Filter
     */
    public function gaussianBlur(){
        return imagefilter($this->image, IMG_FILTER_GAUSSIAN_BLUR);
    }

    /**
     * Turns on selectiveBlur Filter
     */
    public function selectiveBlur(){
        return imagefilter($this->image, IMG_FILTER_SELECTIVE_BLUR);
    }

    /**
     * Turns on sketch Filter
     */
    public function sketch(){
        return imagefilter($this->image, IMG_FILTER_MEAN_REMOVAL);
    }

    /**
     * Adds a vignette
     */
    function vignette(){
        for($x = 0; $x < imagesx($this->image); ++$x){
            for($y = 0; $y < imagesy($this->image); ++$y){  
                $index = imagecolorat($this->image, $x, $y);
                $rgb = imagecolorsforindex($this->image, $index);

                $sharp = 0.4; // 0 - 10 small is sharpnes, 
                $level = 0.7; // 0 - 1 small is brighter
                $l = sin(M_PI / $this->width * $x) * sin(M_PI / $this->height * $y);
                $l = pow($l, $sharp);
                $l = 1 - $level * (1 - $l);
                $rgb['red'] *= $l;
                $rgb['green'] *= $l;
                $rgb['blue'] *= $l;

                $color = imagecolorallocate($this->image, $rgb['red'], $rgb['green'], $rgb['blue']);
                imagesetpixel($this->image, $x, $y, $color);  
            }
        }
        return true;
    }

    /**
     * Pixelate the image
     * @param type $blocksize
     * @param type $advanced
     */
    public function pixelate($blocksize, $advanced=true){
        return imagefilter($this->image, IMG_FILTER_PIXELATE, $blocksize, $advanced);
    }

    /**
     * Adjust smoothness level
     * @param type $level
     */
    public function adjustSmoothness($level){
        return imagefilter($this->image, IMG_FILTER_SMOOTH, $level);
    }

    /**
     * Colorize an image
     * @param type $hexColor
     */
    public function colorize($hexColor, $alpha){
        list($r, $g, $b) = self::convertHexColor($hexColor);
        if($alpha < 0) $alpha = 0;
        if($alpha > 127) $alpha = 127;
        return imagefilter($this->image,  IMG_FILTER_COLORIZE, $r, $g, $b, $alpha);
    }

    /**
     * Outputs the image directly to the browser
     * @param type $output_mimetype ("png", "jpg", or "gif")
     *      (defaults to the original image's mimtype)
     */
    public function output($output_mimetype = null){

        // Get output details
        list($mimetype, $funct, $compression) = $this->getOutputDetails($output_mimetype);

        // Output and clear memory
        header('Content-Type: '.$mimetype);

        // Get and call the image creation funtion
        $funct($this->image, null, $compression);
    }

    /**
     * Destroys the generated image to free up resources,
     * Delete the file if it's a temporary file.
     * should be the last method called as the object is unusable after this.
     */
    public function destroy(){ 
        imagedestroy($this->image); 
        if($this->isTemp) unlink($this->filepath);
    }

    /**
     * Crops the image to the given dimensions, at the given starting point
     * defaults to the top left
     * @param type $width
     * @param type $height
     * @param type $x
     * @param type $y
     */
    public function crop($new_width, $new_height, $x = 0, $y = 0){

        // Get dimensions
        $width = imagesx($this->image);
        $height = imagesy($this->image);

        // Make the dummy image that will become the new image
        $newImg = imagecreatetruecolor($new_width, $new_height);

        // Fill with transparent background
        imagealphablending($newImg, false);
        imagesavealpha($newImg, true);
        $transparent = imagecolorallocatealpha($newImg, 255, 255, 255, 127);
        imagefilledrectangle($newImg, 0, 0, $new_width, $new_height, $transparent);

        // Copy the pixels to the new image
        imagecopy($newImg, $this->image, 0, 0, 0, 0, $width, $height);
        $this->image = $newImg;
        $this->width = $width;
        $this->height = $height;
    }

    /**
     * Create an image from a base64 string
     * @param type $string
     * @return \Image
     */
    public static function createFromBase64($string){

        // decode base64 string
        $imgData = base64_decode(preg_replace('#^data:image/\w+;base64,#i', '', $string));

        // create the image resource
        $formImage = imagecreatefromstring($imgData);

        // Fill with transparent background
        imagealphablending($formImage, false);
        imagesavealpha($formImage, true);
        $transparent = imagecolorallocatealpha($formImage, 255, 255, 255, 127);
        imagefill($formImage, 0, 0, $transparent);

        // Save the image to a temp png file to use in our constructor
        $tmpname = tempnam('/tmp', 'IMG');

        // Generate and save image
        imagepng($formImage, $tmpname, 9);

        // Return an instance of the class
        $img = new Image($tmpname, true);
        return $img;
    } 

    /**
     * Make an image of text
     * @param type $text
     * @param type $size
     * @param type $color
     * @param type $font
     */
    public static function createTextImage($text, $font_size, $color, $font_file, $wrap_width = false){

        // Make sure font file exists
        if(!file_exists($font_file)) throw new Exception("Font file does not exist: {$font_file}");

        // Generate wrapping text
        if(is_numeric($wrap_width) && $wrap_width != 0) 
            $text = self::wrapText($font_size, $font_file, $text, $wrap_width);     

        // Retrieve bounding box:
        $type_space = imagettfbbox($font_size, 0, $font_file, $text);

        // Determine image width and height, 10 pixels are added for 5 pixels padding:
        $image_width = abs($type_space[4] - $type_space[0]) + 10;
        $image_height = abs($type_space[5] - $type_space[1]) + 10;
        $line_height = self::getLineHeight($font_size, $font_file) +10;

        // Create image:
        $image = imagecreatetruecolor($image_width, $image_height);

        // Allocate text and background colors (RGB format):
        $text_color = self::getColor($image, $color);

        // Fill with transparent background
        imagealphablending($image, false);
        imagesavealpha($image, true);
        $transparent = imagecolorallocatealpha($image, 255, 255, 255, 127);
        imagefill($image, 0, 0, $transparent);

        // Fix starting x and y coordinates for the text:
        $x = 5; // Padding of 5 pixels.
        $y = $line_height - 5; // So that the text is vertically centered.

        // Add TrueType text to image:
        imagettftext($image, $font_size, 0, $x, $y, $text_color, $font_file, $text);

        // Save the image to a temp png file to use in our constructor
        $tmpname = tempnam('/tmp', 'IMG');

        // Generate and save image
        imagepng($image, $tmpname, 9);

        // Return an instance of the class
        $img = new Image($tmpname, true);
        $img->crop($image_width, $image_height);
        return $img;
    }

    /**
     * Get output information
     * @param type $output_mimetype
     * @return array($mimetype, $output_funct, $compression)
     */
    private function getOutputDetails($output_mimetype){
        switch(strtoupper($output_mimetype)){
            case('JPG'):
            case('JPEG'):
                $mimetype = 'image/jpeg';
                $funct = 'imagejpeg';
                $compression = 100;
                break;
            case('PNG'):
                $mimetype = 'image/png';
                $funct = 'imagepng';
                $compression = 9;
                break;
            case('GIF'):
                $mimetype = 'image/gif';
                $funct = 'imagegif';
                $compression = null;
                break;
            default:
                $mimetype = $this->mime;
                $funct = $this->imageFunct;
                $compression = $this->compression;
        }
        return array($mimetype, $funct, $compression);
    }

    private static function convertHexColor($hex){
        // Remove the # if therre is one
        $hex = str_replace("#", "", $hex);

        // Convert the hex to rgb
        if(strlen($hex) == 3){
            $r = hexdec(substr($hex, 0, 1) . substr($hex, 0, 1));
            $g = hexdec(substr($hex, 1, 1) . substr($hex, 1, 1));
            $b = hexdec(substr($hex, 2, 1) . substr($hex, 2, 1));
        }else{
            $r = hexdec(substr($hex, 0, 2));
            $g = hexdec(substr($hex, 2, 2));
            $b = hexdec(substr($hex, 4, 2));
        }

        return array($r, $g, $b);
    }

    /**
     * Convert Hex Colors To RGB
     * @param type $image - image identifier
     * @param type $hex - the hex color code
     * @param type $alpha - 0 for solid - 127 for transparent
     * @return type color identifier
     * @throws Exception
     */
    private static function getColor($image, $hex, $alpha=0){
        list($r, $g, $b) = self::convertHexColor($hex);

        // The alpha layer seems to make things gritty, 
        // so let's avoid it if there's no transparency
        $return = ($alpha==0) ? 
            imagecolorallocatealpha($image, $r, $g, $b, $alpha) :
            imagecolorallocate($image, $r, $g, $b) ;

        // Make sure it worked
        if($return === false) throw new Exception("Could not create color $hex.");

        return $return;
    }

    /**
     * Inserts linebreaks to wrap text
     * @param type $fontSize
     * @param type $fontFace
     * @param type $string
     * @param type $width
     * @return string
     */
    private static function wrapText($fontSize, $fontFace, $string, $width){

        $ret = "";
        $arr = explode(" ", $string);

        foreach($arr as $word){
            $testboxWord = imagettfbbox($fontSize, 0, $fontFace, $word);

            // huge word larger than $width, we need to cut it internally until it fits the width
            $len = strlen($word);
            while($testboxWord[2] > $width && $len > 0){
                $word = substr($word, 0, $len);
                $len--;
                $testboxWord = imagettfbbox($fontSize, 0, $fontFace, $word);
            }

            $teststring = $ret . ' ' . $word;
            $testboxString = imagettfbbox($fontSize, 0, $fontFace, $teststring);
            if($testboxString[2] > $width){
                $ret.=($ret == "" ? "" : "\n") . $word;
            }else{
                $ret.=($ret == "" ? "" : ' ') . $word;
            }
        }

        return $ret;
    }

    /**
     * Returns the line height based on the font and font size
     * @param type $fontSize
     * @param type $fontFace
     */
    private static function getLineHeight($fontSize, $fontFace){
        // Arbitrary text is drawn, can't be blank or just a space
        $type_space = imagettfbbox($fontSize, 0, $fontFace, "Robert is awesome!");
        $line_height = abs($type_space[5] - $type_space[1]);
        return $line_height;
    }

}

修改

这是一个完全有效的脚本,可以使用上面的类来完成您想要的操作:

<?php

error_reporting(E_ALL);
ini_set('display_errors', '1');

// Require the class
require "./Image.php";

// Set the text of your image
$text = "This is a test";

// Hijack a font from the web if you don't have a local .ttf file to use
$ttf_font_file = tempnam('/tmp', 'IMG');
$fh = fopen($ttf_font_file, "w");
fwrite($fh, file_get_contents("https://github.com/todylu/monaco.ttf/blob/master/monaco.ttf?raw=true"));
fclose($fh);

// Set font size to something really big so we can scale it down without losing resolution
$font_size = 100;

// And the font color
$color = "#000";

// Create the image
$img = Image::createTextImage($text, $font_size, $color, $ttf_font_file);

// shrink the image to the desired proportions
$img->resize(200, 25);

// output it as a png
$img->output('png');

// destroy the evidence
$img->destroy();