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

标签 javascript php html image canvas

我想将像素化文本(无抗锯齿)放到 HTML Canvas 上。我考虑过使用 PHP 使用 imagettftext 呈现文本,使用负颜色值(禁用抗锯齿),然后将该图像放到 Canvas 上。这很好用,但是我也想垂直或水平拉伸(stretch)文本。有没有办法在 PHP 中执行此操作? (也可以使用其他脚本语言。)

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

最佳答案

我正在从事一个类似的项目,该项目涉及使用 PHP 生成图像并将它们绘制到 Canvas 上。我注意到的几件事是任何透明度都会影响图像的清晰度。如果您使用透明度,最好在 Canvas 上铺一层白色,然后再在 Canvas 上绘制图像。这是我正在上的一门课,也许你可以从中得到一些引用。它也做像素化的事情。

你可以这样使用它:

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

这会将图像输出到浏览器,您可以将其加载到 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;
    }

}

编辑

这是一个完全可用的脚本,可以执行您想要的操作,使用我上面的类:

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

关于javascript - 如何在没有抗锯齿的情况下将缩放文本作为图像,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34180180/

相关文章:

java - 有什么方法可以像在 PHP 中一样声明 'Hybrid' java 的 HashMap

php - laravel 6中如何从本地存储中获取每个用户登录的数据?

javascript - 使用php,ajax从数据库中获取数据

javascript - 单击同一页面中的 <a> 时移动到 <section>

javascript - 选择要在表单中提交的字段

javascript - 使用 JavaScript 在 JSDOM 上发送点击事件

javascript - 如何从 Javascript/jQuery 调用 SOAP WS

jquery - Bootstrap navbar社交媒体图标手机版

html - 9gag电视如何删除嵌入式YouTube视频的标题?

javascript - C# javascript 通过标签和属性点击按钮