Skip to content

Commit 153df35

Browse files
committedAug 26, 2018
Initial commit
0 parents  commit 153df35

5 files changed

+630
-0
lines changed
 

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/vendor

‎class-image-editor-vips.php

+477
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,477 @@
1+
<?php
2+
3+
require_once ABSPATH . WPINC . '/class-wp-image-editor.php';
4+
5+
class Image_Editor_Vips extends \WP_Image_Editor {
6+
/**
7+
* GD Resource.
8+
*
9+
* @var resource
10+
*/
11+
protected $image;
12+
13+
public function __destruct() {
14+
if ( $this->image ) {
15+
// we don't need the original in memory anymore
16+
imagedestroy( $this->image );
17+
}
18+
}
19+
20+
/**
21+
* Checks to see if current environment supports GD.
22+
*
23+
* @since 3.5.0
24+
*
25+
* @static
26+
*
27+
* @param array $args
28+
* @return bool
29+
*/
30+
public static function test( $args = array() ) {
31+
if ( ! extension_loaded('gd') || ! function_exists('gd_info') )
32+
return false;
33+
34+
// On some setups GD library does not provide imagerotate() - Ticket #11536
35+
if ( isset( $args['methods'] ) &&
36+
in_array( 'rotate', $args['methods'] ) &&
37+
! function_exists('imagerotate') ){
38+
39+
return false;
40+
}
41+
42+
return true;
43+
}
44+
45+
/**
46+
* Checks to see if editor supports the mime-type specified.
47+
*
48+
* @since 3.5.0
49+
*
50+
* @static
51+
*
52+
* @param string $mime_type
53+
* @return bool
54+
*/
55+
public static function supports_mime_type( $mime_type ) {
56+
$image_types = imagetypes();
57+
switch( $mime_type ) {
58+
case 'image/jpeg':
59+
return ($image_types & IMG_JPG) != 0;
60+
case 'image/png':
61+
return ($image_types & IMG_PNG) != 0;
62+
case 'image/gif':
63+
return ($image_types & IMG_GIF) != 0;
64+
}
65+
66+
return false;
67+
}
68+
69+
/**
70+
* Loads image from $this->file into new GD Resource.
71+
*
72+
* @since 3.5.0
73+
*
74+
* @return bool|WP_Error True if loaded successfully; WP_Error on failure.
75+
*/
76+
public function load() {
77+
if ( $this->image )
78+
return true;
79+
80+
if ( ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) )
81+
return new WP_Error( 'error_loading_image', __('File doesn&#8217;t exist?'), $this->file );
82+
83+
// Set artificially high because GD uses uncompressed images in memory.
84+
wp_raise_memory_limit( 'image' );
85+
86+
87+
88+
//$this->image = @imagecreatefromstring( file_get_contents( $this->file ) );
89+
$image_file = file_get_contents( $this->file );
90+
$this->image = Jcupitt\Vips\Image::newFromFile($this->file);
91+
92+
//if ( ! is_resource( $this->image ) )
93+
// return new WP_Error( 'invalid_image', __('File is not an image.'), $this->file );
94+
95+
//$size = @getimagesize( $this->file );
96+
//if ( ! $size )
97+
// return new WP_Error( 'invalid_image', __('Could not read image size.'), $this->file );
98+
99+
//if ( function_exists( 'imagealphablending' ) && function_exists( 'imagesavealpha' ) ) {
100+
// imagealphablending( $this->image, false );
101+
// imagesavealpha( $this->image, true );
102+
//}
103+
104+
$this->update_size( $this->image->width, $this->image->height );
105+
106+
$file_info = new finfo(FILEINFO_MIME_TYPE);
107+
$mime_type = $file_info->buffer(file_get_contents($image_file));
108+
$this->mime_type = $mime_type;
109+
110+
return $this->set_quality();
111+
}
112+
113+
/**
114+
* Sets or updates current image size.
115+
*
116+
* @since 3.5.0
117+
*
118+
* @param int $width
119+
* @param int $height
120+
* @return true
121+
*/
122+
protected function update_size( $width = false, $height = false ) {
123+
if ( ! $width )
124+
$width = $this->image->width;
125+
126+
if ( ! $height )
127+
$height = $this->image->height;
128+
129+
return parent::update_size( $width, $height );
130+
}
131+
132+
/**
133+
* Resizes current image.
134+
* Wraps _resize, since _resize returns a GD Resource.
135+
*
136+
* At minimum, either a height or width must be provided.
137+
* If one of the two is set to null, the resize will
138+
* maintain aspect ratio according to the provided dimension.
139+
*
140+
* @since 3.5.0
141+
*
142+
* @param int|null $max_w Image width.
143+
* @param int|null $max_h Image height.
144+
* @param bool $crop
145+
* @return true|WP_Error
146+
*/
147+
public function resize( $max_w, $max_h, $crop = false ) {
148+
if ( ( $this->size['width'] == $max_w ) && ( $this->size['height'] == $max_h ) )
149+
return true;
150+
151+
$resized = $this->_resize( $max_w, $max_h, $crop );
152+
153+
//if ( is_resource( $resized ) ) {
154+
// imagedestroy( $this->image );
155+
$this->image = $resized;
156+
return true;
157+
158+
//} elseif ( is_wp_error( $resized ) )
159+
// return $resized;
160+
161+
//return new WP_Error( 'image_resize_error', __('Image resize failed.'), $this->file );
162+
}
163+
164+
/**
165+
*
166+
* @param int $max_w
167+
* @param int $max_h
168+
* @param bool|array $crop
169+
* @return resource|WP_Error
170+
*/
171+
protected function _resize( $max_w, $max_h, $crop = false ) {
172+
$dims = image_resize_dimensions( $this->size['width'], $this->size['height'], $max_w, $max_h, $crop );
173+
if ( ! $dims ) {
174+
return new WP_Error( 'error_getting_dimensions', __('Could not calculate resized image dimensions'), $this->file );
175+
}
176+
list( $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ) = $dims;
177+
178+
//$resized = wp_imagecreatetruecolor( $dst_w, $dst_h );
179+
//imagecopyresampled( $resized, $this->image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h );
180+
181+
error_log('$src_x ' . $src_x);
182+
error_log('$src_y ' . $src_y);
183+
error_log('$src_w ' . $src_w);
184+
error_log('$src_h ' . $src_h);
185+
error_log('$dst_w ' . $dst_w);
186+
error_log('$dst_h ' . $dst_h);
187+
error_log('$dst_x ' . $dst_x);
188+
error_log('$dst_y ' . $dst_y);
189+
190+
$resized = $this->image->resize(max($dst_h/$src_h, $dst_w/$src_w))->crop($dst_x, $dst_y, $dst_w, $dst_h);
191+
192+
//if ( is_resource( $resized ) ) {
193+
$this->update_size( $dst_w, $dst_h );
194+
return $resized;
195+
//}
196+
197+
//return new WP_Error( 'image_resize_error', __('Image resize failed.'), $this->file );
198+
}
199+
200+
/**
201+
* Resize multiple images from a single source.
202+
*
203+
* @since 3.5.0
204+
*
205+
* @param array $sizes {
206+
* An array of image size arrays. Default sizes are 'small', 'medium', 'medium_large', 'large'.
207+
*
208+
* Either a height or width must be provided.
209+
* If one of the two is set to null, the resize will
210+
* maintain aspect ratio according to the provided dimension.
211+
*
212+
* @type array $size {
213+
* Array of height, width values, and whether to crop.
214+
*
215+
* @type int $width Image width. Optional if `$height` is specified.
216+
* @type int $height Image height. Optional if `$width` is specified.
217+
* @type bool $crop Optional. Whether to crop the image. Default false.
218+
* }
219+
* }
220+
* @return array An array of resized images' metadata by size.
221+
*/
222+
public function multi_resize( $sizes ) {
223+
$metadata = array();
224+
$orig_size = $this->size;
225+
226+
foreach ( $sizes as $size => $size_data ) {
227+
if ( ! isset( $size_data['width'] ) && ! isset( $size_data['height'] ) ) {
228+
continue;
229+
}
230+
231+
if ( ! isset( $size_data['width'] ) ) {
232+
$size_data['width'] = null;
233+
}
234+
if ( ! isset( $size_data['height'] ) ) {
235+
$size_data['height'] = null;
236+
}
237+
238+
if ( ! isset( $size_data['crop'] ) ) {
239+
$size_data['crop'] = false;
240+
}
241+
242+
$image = $this->_resize( $size_data['width'], $size_data['height'], $size_data['crop'] );
243+
$duplicate = ( ( $orig_size['width'] == $size_data['width'] ) && ( $orig_size['height'] == $size_data['height'] ) );
244+
245+
if ( ! is_wp_error( $image ) && ! $duplicate ) {
246+
$resized = $this->_save( $image );
247+
248+
imagedestroy( $image );
249+
250+
if ( ! is_wp_error( $resized ) && $resized ) {
251+
unset( $resized['path'] );
252+
$metadata[$size] = $resized;
253+
}
254+
}
255+
256+
$this->size = $orig_size;
257+
}
258+
259+
return $metadata;
260+
}
261+
262+
/**
263+
* Crops Image.
264+
*
265+
* @since 3.5.0
266+
*
267+
* @param int $src_x The start x position to crop from.
268+
* @param int $src_y The start y position to crop from.
269+
* @param int $src_w The width to crop.
270+
* @param int $src_h The height to crop.
271+
* @param int $dst_w Optional. The destination width.
272+
* @param int $dst_h Optional. The destination height.
273+
* @param bool $src_abs Optional. If the source crop points are absolute.
274+
* @return bool|WP_Error
275+
*/
276+
public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ) {
277+
// If destination width/height isn't specified, use same as
278+
// width/height from source.
279+
if ( ! $dst_w )
280+
$dst_w = $src_w;
281+
if ( ! $dst_h )
282+
$dst_h = $src_h;
283+
284+
$dst = wp_imagecreatetruecolor( $dst_w, $dst_h );
285+
286+
if ( $src_abs ) {
287+
$src_w -= $src_x;
288+
$src_h -= $src_y;
289+
}
290+
291+
if ( function_exists( 'imageantialias' ) )
292+
imageantialias( $dst, true );
293+
294+
imagecopyresampled( $dst, $this->image, 0, 0, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h );
295+
296+
if ( is_resource( $dst ) ) {
297+
imagedestroy( $this->image );
298+
$this->image = $dst;
299+
$this->update_size();
300+
return true;
301+
}
302+
303+
return new WP_Error( 'image_crop_error', __('Image crop failed.'), $this->file );
304+
}
305+
306+
/**
307+
* Rotates current image counter-clockwise by $angle.
308+
* Ported from image-edit.php
309+
*
310+
* @since 3.5.0
311+
*
312+
* @param float $angle
313+
* @return true|WP_Error
314+
*/
315+
public function rotate( $angle ) {
316+
if ( function_exists('imagerotate') ) {
317+
$transparency = imagecolorallocatealpha( $this->image, 255, 255, 255, 127 );
318+
$rotated = imagerotate( $this->image, $angle, $transparency );
319+
320+
if ( is_resource( $rotated ) ) {
321+
imagealphablending( $rotated, true );
322+
imagesavealpha( $rotated, true );
323+
imagedestroy( $this->image );
324+
$this->image = $rotated;
325+
$this->update_size();
326+
return true;
327+
}
328+
}
329+
return new WP_Error( 'image_rotate_error', __('Image rotate failed.'), $this->file );
330+
}
331+
332+
/**
333+
* Flips current image.
334+
*
335+
* @since 3.5.0
336+
*
337+
* @param bool $horz Flip along Horizontal Axis
338+
* @param bool $vert Flip along Vertical Axis
339+
* @return true|WP_Error
340+
*/
341+
public function flip( $horz, $vert ) {
342+
$w = $this->size['width'];
343+
$h = $this->size['height'];
344+
$dst = wp_imagecreatetruecolor( $w, $h );
345+
346+
if ( is_resource( $dst ) ) {
347+
$sx = $vert ? ($w - 1) : 0;
348+
$sy = $horz ? ($h - 1) : 0;
349+
$sw = $vert ? -$w : $w;
350+
$sh = $horz ? -$h : $h;
351+
352+
if ( imagecopyresampled( $dst, $this->image, 0, 0, $sx, $sy, $w, $h, $sw, $sh ) ) {
353+
imagedestroy( $this->image );
354+
$this->image = $dst;
355+
return true;
356+
}
357+
}
358+
return new WP_Error( 'image_flip_error', __('Image flip failed.'), $this->file );
359+
}
360+
361+
/**
362+
* Saves current in-memory image to file.
363+
*
364+
* @since 3.5.0
365+
*
366+
* @param string|null $filename
367+
* @param string|null $mime_type
368+
* @return array|WP_Error {'path'=>string, 'file'=>string, 'width'=>int, 'height'=>int, 'mime-type'=>string}
369+
*/
370+
public function save( $filename = null, $mime_type = null ) {
371+
$saved = $this->_save( $this->image, $filename, $mime_type );
372+
373+
if ( ! is_wp_error( $saved ) ) {
374+
$this->file = $saved['path'];
375+
$this->mime_type = $saved['mime-type'];
376+
}
377+
378+
return $saved;
379+
}
380+
381+
/**
382+
* @param resource $image
383+
* @param string|null $filename
384+
* @param string|null $mime_type
385+
* @return WP_Error|array
386+
*/
387+
protected function _save( $image, $filename = null, $mime_type = null ) {
388+
list( $filename, $extension, $mime_type ) = $this->get_output_format( $filename, $mime_type );
389+
390+
if ( ! $filename )
391+
$filename = $this->generate_filename( null, null, $extension );
392+
393+
$image->writeToFile($filename);
394+
395+
/*if ( 'image/gif' == $mime_type ) {
396+
if ( ! $this->make_image( $filename, 'imagegif', array( $image, $filename ) ) )
397+
return new WP_Error( 'image_save_error', __('Image Editor Save Failed') );
398+
}
399+
elseif ( 'image/png' == $mime_type ) {
400+
// convert from full colors to index colors, like original PNG.
401+
if ( function_exists('imageistruecolor') && ! imageistruecolor( $image ) )
402+
imagetruecolortopalette( $image, false, imagecolorstotal( $image ) );
403+
404+
if ( ! $this->make_image( $filename, 'imagepng', array( $image, $filename ) ) )
405+
return new WP_Error( 'image_save_error', __('Image Editor Save Failed') );
406+
}
407+
elseif ( 'image/jpeg' == $mime_type ) {
408+
if ( ! $this->make_image( $filename, 'imagejpeg', array( $image, $filename, $this->get_quality() ) ) )
409+
return new WP_Error( 'image_save_error', __('Image Editor Save Failed') );
410+
}
411+
else {
412+
return new WP_Error( 'image_save_error', __('Image Editor Save Failed') );
413+
}*/
414+
415+
// Set correct file permissions
416+
$stat = stat( dirname( $filename ) );
417+
$perms = $stat['mode'] & 0000666; //same permissions as parent folder, strip off the executable bits
418+
@ chmod( $filename, $perms );
419+
420+
/**
421+
* Filters the name of the saved image file.
422+
*
423+
* @since 2.6.0
424+
*
425+
* @param string $filename Name of the file.
426+
*/
427+
return array(
428+
'path' => $filename,
429+
'file' => wp_basename( apply_filters( 'image_make_intermediate_size', $filename ) ),
430+
'width' => $this->size['width'],
431+
'height' => $this->size['height'],
432+
'mime-type' => $mime_type,
433+
);
434+
}
435+
436+
/**
437+
* Returns stream of current image.
438+
*
439+
* @since 3.5.0
440+
*
441+
* @param string $mime_type The mime type of the image.
442+
* @return bool True on success, false on failure.
443+
*/
444+
public function stream( $mime_type = null ) {
445+
list( $filename, $extension, $mime_type ) = $this->get_output_format( null, $mime_type );
446+
447+
switch ( $mime_type ) {
448+
case 'image/png':
449+
header( 'Content-Type: image/png' );
450+
return imagepng( $this->image );
451+
case 'image/gif':
452+
header( 'Content-Type: image/gif' );
453+
return imagegif( $this->image );
454+
default:
455+
header( 'Content-Type: image/jpeg' );
456+
return imagejpeg( $this->image, null, $this->get_quality() );
457+
}
458+
}
459+
460+
/**
461+
* Either calls editor's save function or handles file as a stream.
462+
*
463+
* @since 3.5.0
464+
*
465+
* @param string|stream $filename
466+
* @param callable $function
467+
* @param array $arguments
468+
* @return bool
469+
*/
470+
protected function make_image( $filename, $function, $arguments ) {
471+
472+
//if ( wp_is_stream( $filename ) )
473+
// $arguments[1] = null;
474+
475+
//return parent::make_image( $filename, $function, $arguments );
476+
}
477+
}

‎composer.json

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"require": {
3+
"jcupitt/vips": "^1.0"
4+
}
5+
}

‎composer.lock

+121
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vips-image-editor.php

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
/*
4+
Plugin Name: Vips image editor
5+
Plugin URI: https://github.com/joppuyo/wp-vips-image-editor
6+
Description: Power WordPress image operations with Vips
7+
Version: 1.0.0
8+
Author: Johannes Siipola
9+
Author URI: https://siipo.la
10+
License: GPLv2 or later
11+
License URI: http://www.gnu.org/licenses/gpl-2.0.html
12+
*/
13+
14+
// exit if accessed directly
15+
if (!defined('ABSPATH')) {
16+
exit();
17+
}
18+
19+
include 'vendor/autoload.php';
20+
include 'class-image-editor-vips.php';
21+
22+
add_filter('wp_image_editors', function($image_editors) {
23+
array_unshift($image_editors, 'Image_Editor_Vips');
24+
return $image_editors;
25+
}, 9999);
26+

0 commit comments

Comments
 (0)
Please sign in to comment.