简体   繁体   English

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

[英]How to get scaled text as image without anti aliasing

I want to put pixelated text (no anti-aliasing) onto an HTML canvas. 我想将像素化文本(不消除锯齿)放在HTML画布上。 I thought about using PHP to render the text using imagettftext, use negative color values (to disable anti-aliasing) and than put that image onto the canvas. 我考虑过使用PHP使用imagettftext渲染文本,使用负色值(以禁用抗锯齿),然后将图像放到画布上。 This works fine, however I also want to stretch the text vertically or horizontally. 效果很好,但是我也想垂直或水平拉伸文本。 Is there a way to do this in PHP? 有没有办法在PHP中执行此操作? (other scripting languages might also be used.) (也可以使用其他脚本语言。)

Background: While there are ways to draw images onto an HTML canvas without anti-aliasing, I havn't found anything for text. 背景:虽然有多种方法可以在不进行抗锯齿的情况下将图像绘制到HTML画布上,但是我没有找到任何文本内容。 So far, I've used a separate canvas, put my text onto it and manipulated the imageData by setting all alpha values > 128 to 255 and everything else to 0. I then copied that image onto the target canvas. 到目前为止,我已经使用了一个单独的画布,在其中放置了文本,并通过将所有alpha值> 128设置为255,将所有其他值设置为0来操纵imageData。然后,我将该图像复制到了目标画布上。 The problem here is, that especially for small font sizes the resulting characters differ a lot and the line thickness of the charaters are different. 这里的问题是,特别是对于较小的字体,生成的字符相差很大,而字符的线宽也不同。 Example output . 示例输出 (The example is magnified. See the different thickness for "l" and the different rendering for "o".) So any idea how to get crisp non anti-aliased text onto the canvas or how to create images with crisp and stretched text are highly welcome. (示例被放大。请参见“ l”的不同厚度和“ o”的不同渲染。)因此,如何将清晰的非抗锯齿文本粘贴到画布上或如何使用清晰且拉伸的文本创建图像的任何想法都是非常欢迎。

I'm working on a similar project that involves generating images with PHP and drawing them to the canvas. 我正在一个类似的项目中,涉及使用PHP生成图像并将其绘制到画布上。 A few things I've noticed is that any transparency can mess with the crispness of the image. 我注意到的几件事是,任何透明度都会破坏图像的清晰度。 If you're working with transparency it's good idea to lay a flat layer of white down over the canvas before drawing your images on it. 如果您要使用透明性,则最好在画布上绘制白色平整的一层,然后再在其上绘制图像。 Here's a class I'm working on, maybe you can get some reference from it. 这是我正在研究的课程,也许您可​​以从中获得一些参考。 It does the pixelation thing too. 它也做像素化。

You can use it like this: 您可以像这样使用它:

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

That will output an image to the browser that you can load into the canvas. 这会将图像输出到浏览器,您可以将其加载到画布中。

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

}

Edit 编辑

Here's a fully working script that does what you want, using my class above: 这是一个完全正常工作的脚本,使用上面的类可以满足您的需求:

<?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();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM