3 * StatusNet, the distributed open-source microblogging tool
5 * Abstraction for an image file
9 * LICENCE: This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU Affero General Public License as published by
11 * the Free Software Foundation, either version 3 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Affero General Public License for more details.
19 * You should have received a copy of the GNU Affero General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>.
24 * @author Evan Prodromou <evan@status.net>
25 * @author Zach Copley <zach@status.net>
26 * @copyright 2008-2009 StatusNet, Inc.
27 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
28 * @link http://status.net/
31 if (!defined('STATUSNET') && !defined('LACONICA')) {
36 * A wrapper on uploaded files
38 * Makes it slightly easier to accept an image file from upload.
42 * @author Evan Prodromou <evan@status.net>
43 * @author Zach Copley <zach@status.net>
44 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
45 * @link http://status.net/
57 function __construct($id=null, $filepath=null, $type=null, $width=null, $height=null)
60 $this->filepath = $filepath;
62 $info = @getimagesize($this->filepath);
65 ($info[2] == IMAGETYPE_GIF && function_exists('imagecreatefromgif')) ||
66 ($info[2] == IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) ||
67 $info[2] == IMAGETYPE_BMP ||
68 ($info[2] == IMAGETYPE_WBMP && function_exists('imagecreatefromwbmp')) ||
69 ($info[2] == IMAGETYPE_XBM && function_exists('imagecreatefromxbm')) ||
70 ($info[2] == IMAGETYPE_PNG && function_exists('imagecreatefrompng')))) {
72 // TRANS: Exception thrown when trying to upload an unsupported image file format.
73 throw new Exception(_('Unsupported image file format.'));
77 $this->type = ($info) ? $info[2]:$type;
78 $this->width = ($info) ? $info[0]:$width;
79 $this->height = ($info) ? $info[1]:$height;
82 static function fromUpload($param='upload')
84 switch ($_FILES[$param]['error']) {
85 case UPLOAD_ERR_OK: // success, jump out
87 case UPLOAD_ERR_INI_SIZE:
88 case UPLOAD_ERR_FORM_SIZE:
89 // TRANS: Exception thrown when too large a file is uploaded.
90 // TRANS: %s is the maximum file size, for example "500b", "10kB" or "2MB".
91 throw new Exception(sprintf(_('That file is too big. The maximum file size is %s.'),
92 ImageFile::maxFileSize()));
94 case UPLOAD_ERR_PARTIAL:
95 @unlink($_FILES[$param]['tmp_name']);
96 // TRANS: Exception thrown when uploading an image and that action could not be completed.
97 throw new Exception(_('Partial upload.'));
99 case UPLOAD_ERR_NO_FILE:
100 // No file; probably just a non-AJAX submission.
103 common_log(LOG_ERR, __METHOD__ . ": Unknown upload error " .
104 $_FILES[$param]['error']);
105 // TRANS: Exception thrown when uploading an image fails for an unknown reason.
106 throw new Exception(_('System error uploading file.'));
110 $info = @getimagesize($_FILES[$param]['tmp_name']);
113 @unlink($_FILES[$param]['tmp_name']);
114 // TRANS: Exception thrown when uploading a file as image that is not an image or is a corrupt file.
115 throw new Exception(_('Not an image or corrupt file.'));
119 return new ImageFile(null, $_FILES[$param]['tmp_name']);
123 * Compat interface for old code generating avatar thumbnails...
124 * Saves the scaled file directly into the avatar area.
126 * @param int $size target width & height -- must be square
127 * @param int $x (default 0) upper-left corner to crop from
128 * @param int $y (default 0) upper-left corner to crop from
129 * @param int $w (default full) width of image area to crop
130 * @param int $h (default full) height of image area to crop
131 * @return string filename
133 function resize($size, $x = 0, $y = 0, $w = null, $h = null)
135 $targetType = $this->preferredType();
136 $outname = Avatar::filename($this->id,
137 image_type_to_extension($targetType),
140 $outpath = Avatar::path($outname);
141 $this->resizeTo($outpath, $size, $size, $x, $y, $w, $h);
146 * Copy the image file to the given destination.
147 * For obscure formats, this will automatically convert to PNG;
148 * otherwise the original file will be copied as-is.
150 * @param string $outpath
151 * @return string filename
153 function copyTo($outpath)
155 return $this->resizeTo($outpath, $this->width, $this->height);
159 * Create and save a thumbnail image.
161 * @param string $outpath
162 * @param int $width target width
163 * @param int $height target height
164 * @param int $x (default 0) upper-left corner to crop from
165 * @param int $y (default 0) upper-left corner to crop from
166 * @param int $w (default full) width of image area to crop
167 * @param int $h (default full) height of image area to crop
168 * @return string full local filesystem filename
170 function resizeTo($outpath, $width, $height, $x=0, $y=0, $w=null, $h=null)
172 $w = ($w === null) ? $this->width:$w;
173 $h = ($h === null) ? $this->height:$h;
174 $targetType = $this->preferredType();
176 if (!file_exists($this->filepath)) {
177 // TRANS: Exception thrown during resize when image has been registered as present, but is no longer there.
178 throw new Exception(_('Lost our file.'));
182 // Don't crop/scale if it isn't necessary
183 if ($width === $this->width
184 && $height === $this->height
187 && $w === $this->width
188 && $h === $this->height
189 && $this->type == $targetType) {
191 @copy($this->filepath, $outpath);
195 switch ($this->type) {
197 $image_src = imagecreatefromgif($this->filepath);
200 $image_src = imagecreatefromjpeg($this->filepath);
203 $image_src = imagecreatefrompng($this->filepath);
206 $image_src = imagecreatefrombmp($this->filepath);
209 $image_src = imagecreatefromwbmp($this->filepath);
212 $image_src = imagecreatefromxbm($this->filepath);
215 // TRANS: Exception thrown when trying to resize an unknown file type.
216 throw new Exception(_('Unknown file type'));
220 $image_dest = imagecreatetruecolor($width, $height);
222 if ($this->type == IMAGETYPE_GIF || $this->type == IMAGETYPE_PNG || $this->type == IMAGETYPE_BMP) {
224 $transparent_idx = imagecolortransparent($image_src);
226 if ($transparent_idx >= 0) {
228 $transparent_color = imagecolorsforindex($image_src, $transparent_idx);
229 $transparent_idx = imagecolorallocate($image_dest, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']);
230 imagefill($image_dest, 0, 0, $transparent_idx);
231 imagecolortransparent($image_dest, $transparent_idx);
233 } elseif ($this->type == IMAGETYPE_PNG) {
235 imagealphablending($image_dest, false);
236 $transparent = imagecolorallocatealpha($image_dest, 0, 0, 0, 127);
237 imagefill($image_dest, 0, 0, $transparent);
238 imagesavealpha($image_dest, true);
243 imagecopyresampled($image_dest, $image_src, 0, 0, $x, $y, $width, $height, $w, $h);
245 switch ($targetType) {
247 imagegif($image_dest, $outpath);
250 imagejpeg($image_dest, $outpath, 100);
253 imagepng($image_dest, $outpath);
256 // TRANS: Exception thrown when trying resize an unknown file type.
257 throw new Exception(_('Unknown file type'));
261 imagedestroy($image_src);
262 imagedestroy($image_dest);
268 * Several obscure file types should be normalized to PNG on resize.
270 * @fixme consider flattening anything not GIF or JPEG to PNG
273 function preferredType()
275 if($this->type == IMAGETYPE_BMP) {
276 //we don't want to save BMP... it's an inefficient, rare, antiquated format
278 return IMAGETYPE_PNG;
279 } else if($this->type == IMAGETYPE_WBMP) {
280 //we don't want to save WBMP... it's a rare format that we can't guarantee clients will support
282 return IMAGETYPE_PNG;
283 } else if($this->type == IMAGETYPE_XBM) {
284 //we don't want to save XBM... it's a rare format that we can't guarantee clients will support
286 return IMAGETYPE_PNG;
293 @unlink($this->filename);
296 static function maxFileSize()
298 $value = ImageFile::maxFileSizeInt();
300 if ($value > 1024 * 1024) {
301 $value = $value/(1024*1024);
302 // TRANS: Number of megabytes. %d is the number.
303 return sprintf(_m('%dMB','%dMB',$value),$value);
304 } else if ($value > 1024) {
305 $value = $value/1024;
306 // TRANS: Number of kilobytes. %d is the number.
307 return sprintf(_m('%dkB','%dkB',$value),$value);
309 // TRANS: Number of bytes. %d is the number.
310 return sprintf(_m('%dB','%dB',$value),$value);
314 static function maxFileSizeInt()
316 return min(ImageFile::strToInt(ini_get('post_max_size')),
317 ImageFile::strToInt(ini_get('upload_max_filesize')),
318 ImageFile::strToInt(ini_get('memory_limit')));
321 static function strToInt($str)
323 $unit = substr($str, -1);
324 $num = substr($str, 0, -1);
326 switch(strtoupper($unit)){
339 //PHP doesn't (as of 2/24/2010) have an imagecreatefrombmp so conditionally define one
340 if(!function_exists('imagecreatefrombmp')){
341 //taken shamelessly from http://www.php.net/manual/en/function.imagecreatefromwbmp.php#86214
342 function imagecreatefrombmp($p_sFile)
344 // Load the image into a string
345 $file = fopen($p_sFile,"rb");
346 $read = fread($file,10);
347 while(!feof($file)&&($read<>""))
348 $read .= fread($file,1024);
350 $temp = unpack("H*",$read);
352 $header = substr($hex,0,108);
354 // Process the header
355 // Structure: http://www.fastgraph.com/help/bmp_header_format.html
356 if (substr($header,0,4)=="424d")
358 // Cut it in parts of 2 bytes
359 $header_parts = str_split($header,2);
361 // Get the width 4 bytes
362 $width = hexdec($header_parts[19].$header_parts[18]);
364 // Get the height 4 bytes
365 $height = hexdec($header_parts[23].$header_parts[22]);
367 // Unset the header params
368 unset($header_parts);
371 // Define starting X and Y
376 $image = imagecreatetruecolor($width,$height);
378 // Grab the body from the image
379 $body = substr($hex,108);
381 // Calculate if padding at the end-line is needed
382 // Divided by two to keep overview.
383 // 1 byte = 2 HEX-chars
384 $body_size = (strlen($body)/2);
385 $header_size = ($width*$height);
387 // Use end-line padding? Only when needed
388 $usePadding = ($body_size>($header_size*3)+4);
390 // Using a for-loop with index-calculation instaid of str_split to avoid large memory consumption
391 // Calculate the next DWORD-position in the body
392 for ($i=0;$i<$body_size;$i+=3)
394 // Calculate line-ending and padding
397 // If padding needed, ignore image-padding
398 // Shift i to the ending of the current 32-bit-block
402 // Reset horizontal position
405 // Raise the height-position (bottom-up)
408 // Reached the image-height? Break the for-loop
413 // Calculation of the RGB-pixel (defined as BGR in image-data)
414 // Define $i_pos as absolute position in the body
416 $r = hexdec($body[$i_pos+4].$body[$i_pos+5]);
417 $g = hexdec($body[$i_pos+2].$body[$i_pos+3]);
418 $b = hexdec($body[$i_pos].$body[$i_pos+1]);
420 // Calculate and draw the pixel
421 $color = imagecolorallocate($image,$r,$g,$b);
422 imagesetpixel($image,$x,$height-$y,$color);
424 // Raise the horizontal position
428 // Unset the body / free the memory
431 // Return image-object