|
| 1 | +<?php |
| 2 | + |
| 3 | + /** |
| 4 | + * hacked up version of php-respimg <https://github.com/nwtn/php-respimg> |
| 5 | + * |
| 6 | + * @package wp-respimg |
| 7 | + * @version 0.0.1 |
| 8 | + */ |
| 9 | + |
| 10 | + if (class_exists('Imagick')) { |
| 11 | + class RespimgImagick extends Imagick { } |
| 12 | + } else { |
| 13 | + class RespimgImagick { } |
| 14 | + } |
| 15 | + |
| 16 | + |
| 17 | + /** |
| 18 | + * An Imagick extension to provide better (higher quality, lower file size) image resizes. |
| 19 | + * |
| 20 | + * This class extends Imagick (<http://php.net/manual/en/book.imagick.php>) based on |
| 21 | + * research into optimal image resizing techniques (<https://github.com/nwtn/image-resize-tests>). |
| 22 | + * |
| 23 | + * Using these methods with their default settings should provide image resizing that is |
| 24 | + * visually indistinguishable from Photoshop’s “Save for Web…”, but at lower file sizes. |
| 25 | + * |
| 26 | + * @author David Newton <david@davidnewton.ca> |
| 27 | + * @copyright 2015 David Newton |
| 28 | + * @license https://raw.githubusercontent.com/nwtn/php-respimg/master/LICENSE MIT |
| 29 | + * @version 1.0.0 |
| 30 | + */ |
| 31 | + |
| 32 | + class Respimg extends RespimgImagick { |
| 33 | + |
| 34 | + /** |
| 35 | + * Resizes the image using smart defaults for high quality and low file size. |
| 36 | + * |
| 37 | + * This function is basically equivalent to: |
| 38 | + * |
| 39 | + * $optim == true: `mogrify -path OUTPUT_PATH -filter Triangle -define filter:support=2.0 -thumbnail OUTPUT_WIDTH -unsharp 0.25x0.08+8.3+0.045 -dither None -posterize 136 -quality 82 -define jpeg:fancy-upsampling=off -define png:compression-filter=5 -define png:compression-level=9 -define png:compression-strategy=1 -define png:exclude-chunk=all -interlace none -colorspace sRGB INPUT_PATH` |
| 40 | + * |
| 41 | + * $optim == false: `mogrify -path OUTPUT_PATH -filter Triangle -define filter:support=2.0 -thumbnail OUTPUT_WIDTH -unsharp 0.25x0.25+8+0.065 -dither None -posterize 136 -quality 82 -define jpeg:fancy-upsampling=off -define png:compression-filter=5 -define png:compression-level=9 -define png:compression-strategy=1 -define png:exclude-chunk=all -interlace none -colorspace sRGB -strip INPUT_PATH` |
| 42 | + * |
| 43 | + * @access public |
| 44 | + * |
| 45 | + * @param integer $columns The number of columns in the output image. 0 = maintain aspect ratio based on $rows. |
| 46 | + * @param integer $rows The number of rows in the output image. 0 = maintain aspect ratio based on $columns. |
| 47 | + * @param bool $optim Whether you intend to perform optimization on the resulting image. Note that setting this to `true` doesn’t actually perform any optimization. |
| 48 | + */ |
| 49 | + |
| 50 | + public function smartResize($columns, $rows, $optim = false) { |
| 51 | + |
| 52 | + $this->setOption('filter:support', '2.0'); |
| 53 | + $this->thumbnailImage($columns, $rows, false, false, Imagick::FILTER_TRIANGLE); |
| 54 | + if ($optim) { |
| 55 | + $this->unsharpMaskImage(0.25, 0.08, 8.3, 0.045); |
| 56 | + } else { |
| 57 | + $this->unsharpMaskImage(0.25, 0.25, 8, 0.065); |
| 58 | + } |
| 59 | + $this->posterizeImage(136, false); |
| 60 | + $this->setImageCompressionQuality(82); |
| 61 | + $this->setOption('jpeg:fancy-upsampling', 'off'); |
| 62 | + $this->setOption('png:compression-filter', '5'); |
| 63 | + $this->setOption('png:compression-level', '9'); |
| 64 | + $this->setOption('png:compression-strategy', '1'); |
| 65 | + $this->setOption('png:exclude-chunk', 'all'); |
| 66 | + $this->setInterlaceScheme(Imagick::INTERLACE_NO); |
| 67 | + $this->setColorspace(Imagick::COLORSPACE_SRGB); |
| 68 | + if (!$optim) { |
| 69 | + $this->stripImage(); |
| 70 | + } |
| 71 | + |
| 72 | + } |
| 73 | + |
| 74 | + |
| 75 | + /** |
| 76 | + * Changes the size of an image to the given dimensions and removes any associated profiles. |
| 77 | + * |
| 78 | + * `thumbnailImage` changes the size of an image to the given dimensions and |
| 79 | + * removes any associated profiles. The goal is to produce small low cost |
| 80 | + * thumbnail images suited for display on the Web. |
| 81 | + * |
| 82 | + * With the original Imagick thumbnailImage implementation, there is no way to choose a |
| 83 | + * resampling filter. This class recreates Imagick’s C implementation and adds this |
| 84 | + * additional feature. |
| 85 | + * |
| 86 | + * Note: <https://github.com/mkoppanen/imagick/issues/90> has been filed for this issue. |
| 87 | + * |
| 88 | + * @access public |
| 89 | + * |
| 90 | + * @param integer $columns The number of columns in the output image. 0 = maintain aspect ratio based on $rows. |
| 91 | + * @param integer $rows The number of rows in the output image. 0 = maintain aspect ratio based on $columns. |
| 92 | + * @param bool $bestfit Treat $columns and $rows as a bounding box in which to fit the image. |
| 93 | + * @param bool $fill Fill in the bounding box with the background colour. |
| 94 | + * @param integer $filter The resampling filter to use. Refer to the list of filter constants at <http://php.net/manual/en/imagick.constants.php>. |
| 95 | + * |
| 96 | + * @return bool Indicates whether the operation was performed successfully. |
| 97 | + */ |
| 98 | + |
| 99 | + public function thumbnailImage($columns, $rows, $bestfit = false, $fill = false, $filter = Imagick::FILTER_TRIANGLE) { |
| 100 | + |
| 101 | + // sample factor; defined in original ImageMagick thumbnailImage function |
| 102 | + // the scale to which the image should be resized using the `sample` function |
| 103 | + $SampleFactor = 5; |
| 104 | + |
| 105 | + // filter whitelist |
| 106 | + $filters = array( |
| 107 | + Imagick::FILTER_POINT, |
| 108 | + Imagick::FILTER_BOX, |
| 109 | + Imagick::FILTER_TRIANGLE, |
| 110 | + Imagick::FILTER_HERMITE, |
| 111 | + Imagick::FILTER_HANNING, |
| 112 | + Imagick::FILTER_HAMMING, |
| 113 | + Imagick::FILTER_BLACKMAN, |
| 114 | + Imagick::FILTER_GAUSSIAN, |
| 115 | + Imagick::FILTER_QUADRATIC, |
| 116 | + Imagick::FILTER_CUBIC, |
| 117 | + Imagick::FILTER_CATROM, |
| 118 | + Imagick::FILTER_MITCHELL, |
| 119 | + Imagick::FILTER_LANCZOS, |
| 120 | + Imagick::FILTER_BESSEL, |
| 121 | + Imagick::FILTER_SINC |
| 122 | + ); |
| 123 | + |
| 124 | + // Parse parameters given to function |
| 125 | + $columns = (double) ($columns); |
| 126 | + $rows = (double) ($rows); |
| 127 | + $bestfit = (bool) $bestfit; |
| 128 | + $fill = (bool) $fill; |
| 129 | + |
| 130 | + // We can’t resize to (0,0) |
| 131 | + if ($rows < 1 && $columns < 1) { |
| 132 | + return false; |
| 133 | + } |
| 134 | + |
| 135 | + // Set a default filter if an acceptable one wasn’t passed |
| 136 | + if (!in_array($filter, $filters)) { |
| 137 | + $filter = Imagick::FILTER_TRIANGLE; |
| 138 | + } |
| 139 | + |
| 140 | + // figure out the output width and height |
| 141 | + $width = (double) $this->getImageWidth(); |
| 142 | + $height = (double) $this->getImageHeight(); |
| 143 | + $new_width = $columns; |
| 144 | + $new_height = $rows; |
| 145 | + |
| 146 | + $x_factor = $columns / $width; |
| 147 | + $y_factor = $rows / $height; |
| 148 | + if ($rows < 1) { |
| 149 | + $new_height = round($x_factor * $height); |
| 150 | + } elseif ($columns < 1) { |
| 151 | + $new_width = round($y_factor * $width); |
| 152 | + } |
| 153 | + |
| 154 | + // if bestfit is true, the new_width/new_height of the image will be different than |
| 155 | + // the columns/rows parameters; those will define a bounding box in which the image will be fit |
| 156 | + if ($bestfit && $x_factor > $y_factor) { |
| 157 | + $x_factor = $y_factor; |
| 158 | + $new_width = round($y_factor * $width); |
| 159 | + } elseif ($bestfit && $y_factor > $x_factor) { |
| 160 | + $y_factor = $x_factor; |
| 161 | + $new_height = round($x_factor * $height); |
| 162 | + } |
| 163 | + if ($new_width < 1) { |
| 164 | + $new_width = 1; |
| 165 | + } |
| 166 | + if ($new_height < 1) { |
| 167 | + $new_height = 1; |
| 168 | + } |
| 169 | + |
| 170 | + // if we’re resizing the image to more than about 1/3 it’s original size |
| 171 | + // then just use the resize function |
| 172 | + if (($x_factor * $y_factor) > 0.1) { |
| 173 | + $this->resizeImage($new_width, $new_height, $filter, 1); |
| 174 | + |
| 175 | + // if we’d be using sample to scale to smaller than 128x128, just use resize |
| 176 | + } elseif ((($SampleFactor * $new_width) < 128) || (($SampleFactor * $new_height) < 128)) { |
| 177 | + $this->resizeImage($new_width, $new_height, $filter, 1); |
| 178 | + |
| 179 | + // otherwise, use sample first, then resize |
| 180 | + } else { |
| 181 | + $this->sampleImage($SampleFactor * $new_width, $SampleFactor * $new_height); |
| 182 | + $this->resizeImage($new_width, $new_height, $filter, 1); |
| 183 | + } |
| 184 | + |
| 185 | + // if the alpha channel is not defined, make it opaque |
| 186 | + if ($this->getImageAlphaChannel() == Imagick::ALPHACHANNEL_UNDEFINED) { |
| 187 | + $this->setImageAlphaChannel(Imagick::ALPHACHANNEL_OPAQUE); |
| 188 | + } |
| 189 | + |
| 190 | + // set the image’s bit depth to 8 bits |
| 191 | + $this->setImageDepth(8); |
| 192 | + |
| 193 | + // turn off interlacing |
| 194 | + $this->setInterlaceScheme(Imagick::INTERLACE_NO); |
| 195 | + |
| 196 | + // Strip all profiles except color profiles. |
| 197 | + foreach ($this->getImageProfiles('*', true) as $key => $value) { |
| 198 | + if ($key != 'icc' && $key != 'icm') { |
| 199 | + $this->removeImageProfile($key); |
| 200 | + } |
| 201 | + } |
| 202 | + |
| 203 | + if (method_exists($this, 'deleteImageProperty')) { |
| 204 | + $this->deleteImageProperty('comment'); |
| 205 | + $this->deleteImageProperty('Thumb::URI'); |
| 206 | + $this->deleteImageProperty('Thumb::MTime'); |
| 207 | + $this->deleteImageProperty('Thumb::Size'); |
| 208 | + $this->deleteImageProperty('Thumb::Mimetype'); |
| 209 | + $this->deleteImageProperty('software'); |
| 210 | + $this->deleteImageProperty('Thumb::Image::Width'); |
| 211 | + $this->deleteImageProperty('Thumb::Image::Height'); |
| 212 | + $this->deleteImageProperty('Thumb::Document::Pages'); |
| 213 | + } else { |
| 214 | + $this->setImageProperty('comment', ''); |
| 215 | + $this->setImageProperty('Thumb::URI', ''); |
| 216 | + $this->setImageProperty('Thumb::MTime', ''); |
| 217 | + $this->setImageProperty('Thumb::Size', ''); |
| 218 | + $this->setImageProperty('Thumb::Mimetype', ''); |
| 219 | + $this->setImageProperty('software', ''); |
| 220 | + $this->setImageProperty('Thumb::Image::Width', ''); |
| 221 | + $this->setImageProperty('Thumb::Image::Height', ''); |
| 222 | + $this->setImageProperty('Thumb::Document::Pages', ''); |
| 223 | + } |
| 224 | + |
| 225 | + // In case user wants to fill use extent for it rather than creating a new canvas |
| 226 | + // …fill out the bounding box |
| 227 | + if ($bestfit && $fill && ($new_width != $columns || $new_height != $rows)) { |
| 228 | + $extent_x = 0; |
| 229 | + $extent_y = 0; |
| 230 | + |
| 231 | + if ($columns > $new_width) { |
| 232 | + $extent_x = ($columns - $new_width) / 2; |
| 233 | + } |
| 234 | + if ($rows > $new_height) { |
| 235 | + $extent_y = ($rows - $new_height) / 2; |
| 236 | + } |
| 237 | + |
| 238 | + $this->extentImage($columns, $rows, 0 - $extent_x, $extent_y); |
| 239 | + } |
| 240 | + |
| 241 | + return true; |
| 242 | + |
| 243 | + } |
| 244 | + |
| 245 | + } |
0 commit comments