3

OK so I realise there are thousands of questions relating to this sort of thing however no answers really seem to cover my needs. So here is my problem.

I have a real estate CRM system that several companies use to store their property details and images which I then serve onto various different property portals on which they advertise said properties. Now they obviously require there photos to be watermarked my system allows them to do so in two ways by having a transparent one in the center or a opaque smaller one in the bottom right corner. Depending on what setting this is on this gets reflected in the image URLs in the XML feed that gets generated and sent to various sites. They can of course then change there logo etc freely without issue of having hardcopies of images etc.

Now when the bottom right watermark is being used there are no issues as the php processing is much less however for the transparent when some portals grab all the images and I am talking thousands+ (and increasing by the day and by each company that joins) it naturally overloads the VPS server and inmotionhosting cut me off. This is the 3rd party script I am using which does the job perfectly just uses too many resources. Any ideas for improvements? I know people mention cacheing but each image is different and probably only requested once so would it benefit that much? All your help is appreciated as it is the one headache I have when it comes to scaling the system. Thanks in advance!

<?php
class watermark{

# given two images, return a blended watermarked image
function create_watermark( $main_img_obj, $watermark_img_obj, $alpha_level = 100 ) {
    $alpha_level    /= 100; # convert 0-100 (%) alpha to decimal

    # calculate our images dimensions
    $main_img_obj_w = imagesx( $main_img_obj );
    $main_img_obj_h = imagesy( $main_img_obj );
    $watermark_img_obj_w    = imagesx( $watermark_img_obj );
    $watermark_img_obj_h    = imagesy( $watermark_img_obj );

    # determine center position coordinates
    $main_img_obj_min_x = floor( ( $main_img_obj_w / 2 ) - ( $watermark_img_obj_w / 2 ) );
    $main_img_obj_max_x = ceil( ( $main_img_obj_w / 2 ) + ( $watermark_img_obj_w / 2 ) );
    $main_img_obj_min_y = floor( ( $main_img_obj_h / 2 ) - ( $watermark_img_obj_h / 2 ) );
    $main_img_obj_max_y = ceil( ( $main_img_obj_h / 2 ) + ( $watermark_img_obj_h / 2 ) ); 


    # create new image to hold merged changes
    $return_img = imagecreatetruecolor( $main_img_obj_w, $main_img_obj_h );

    # walk through main image
    for( $y = 0; $y < $main_img_obj_h; $y++ ) {
        for( $x = 0; $x < $main_img_obj_w; $x++ ) {
            $return_color   = NULL;


            # determine the correct pixel location within our watermark
            $watermark_x    = $x - $main_img_obj_min_x;
            $watermark_y    = $y - $main_img_obj_min_y;

            # fetch color information for both of our images
            $main_rgb = imagecolorsforindex( $main_img_obj, imagecolorat( $main_img_obj, $x, $y ) );

            # if our watermark has a non-transparent value at this pixel intersection
            # and we're still within the bounds of the watermark image
            if (    $watermark_x >= 0 && $watermark_x < $watermark_img_obj_w &&
                        $watermark_y >= 0 && $watermark_y < $watermark_img_obj_h ) {
                $watermark_rbg = imagecolorsforindex( $watermark_img_obj, imagecolorat( $watermark_img_obj, $watermark_x, $watermark_y ) );

                # using image alpha, and user specified alpha, calculate average
                $watermark_alpha    = round( ( ( 127 - $watermark_rbg['alpha'] ) / 127 ), 2 );
                $watermark_alpha    = $watermark_alpha * $alpha_level;

                # calculate the color 'average' between the two - taking into account the specified alpha level
                $avg_red        = $this->_get_ave_color( $main_rgb['red'],      $watermark_rbg['red'],      $watermark_alpha );
                $avg_green  = $this->_get_ave_color( $main_rgb['green'],    $watermark_rbg['green'],    $watermark_alpha );
                $avg_blue       = $this->_get_ave_color( $main_rgb['blue'], $watermark_rbg['blue'],     $watermark_alpha );

                # calculate a color index value using the average RGB values we've determined
                $return_color   = $this->_get_image_color( $return_img, $avg_red, $avg_green, $avg_blue );

            # if we're not dealing with an average color here, then let's just copy over the main color
            } else {
                $return_color   = imagecolorat( $main_img_obj, $x, $y );

            } # END if watermark

            # draw the appropriate color onto the return image
            imagesetpixel( $return_img, $x, $y, $return_color );

        } # END for each X pixel
    } # END for each Y pixel

    # return the resulting, watermarked image for display
    return $return_img;

} # END create_watermark()

# average two colors given an alpha
function _get_ave_color( $color_a, $color_b, $alpha_level ) {
    return round( ( ( $color_a * ( 1 - $alpha_level ) ) + ( $color_b    * $alpha_level ) ) );
} # END _get_ave_color()

# return closest pallette-color match for RGB values
function _get_image_color($im, $r, $g, $b) {
    $c=imagecolorexact($im, $r, $g, $b);
    if ($c!=-1) return $c;
    $c=imagecolorallocate($im, $r, $g, $b);
    if ($c!=-1) return $c;
    return imagecolorclosest($im, $r, $g, $b);
} # EBD _get_image_color()

} # END watermark API
?>

<?php

# include our watermerking class
include 'api.watermark.php';
$watermark          = new watermark();

# create image objects using our user-specified images
# NOTE: we're just going to assume we're dealing with a JPG and a PNG here - for example purposes



$imgtype = exif_imagetype ( $_GET['main'] );

if($imgtype === 3){
$main_img_obj               = imagecreatefrompng( $_GET['main']);
}else if($imgtype === 2){
$main_img_obj               = imagecreatefromjpeg(  $_GET['main']);
}else if($imgtype === 1){
$main_img_obj               = imagecreatefromgif(   $_GET['main']);
}else if($imgtype === 6){
$main_img_obj               = imagecreatefrombmp(   $_GET['main']);
}



$watermark_img_obj  = imagecreatefrompng(   $_GET['watermark']);

# create our watermarked image - set 66% alpha transparency for our watermark
$return_img_obj         = $watermark->create_watermark( $main_img_obj, $watermark_img_obj, 30 );

# display our watermarked image - first telling the browser that it's a JPEG, 
# and that it should be displayed inline
if(($imgtype === 3)or($imgtype === 2)){
header( 'Content-Type: image/png','Content-Type: image/jpeg');
}else if(($imgtype === 1)or($imgtype === 6)){
header( 'Content-Type: image/gif','Content-Type: image/bmp');   
}

header( 'Content-Disposition: inline; filename=' . $_GET['src'] );
imagejpeg( $return_img_obj, '', 50 );

?>
hakre
  • 193,403
  • 52
  • 435
  • 836
Ash
  • 397
  • 1
  • 3
  • 10
  • How large are these images? (Dimensions I mean?). Also, I'd suggest during the upload you convert all images to a common format (IE jpeg or gif), whichever leaves you with best quality vs processing time. – BenOfTheNorth Feb 25 '12 at 19:16
  • I resize them all on upload to max width of 800px - processing time on upload not an issue really just whatever is going to be best for the watermarking. I can start to convert images to jpg from now I guess however a lot already in system I would say 95% are jpg though – Ash Feb 25 '12 at 20:16
  • The reason I say about the file type is because if they are all one type, you can skip reading the exif for a start. – BenOfTheNorth Feb 25 '12 at 20:18
  • OK this is a good idea anywhere i can reduce the processing the better but I don't think it will solve the problem entirely though – Ash Feb 26 '12 at 14:29
  • Perhaps this usage case is a good fit for batch processing, either pre upload, or once uploaded but before the image is available to wider users. That way you can spread the effort of watermarking over a period of time, and then when the images are requested all at the same time there isn't a massive spike in effort – James Holwell Mar 16 '12 at 00:46

2 Answers2

0

I think you are not caching the watermarked image which you probably re-use every time.

Besides that I don't think PHP is the tool to use for this kind of work. I would look for an external library that you can use in PHP to do this. PHP is fine, just not for this kind of work. You can check out relative performance for a lot of memory/cpu intensive tasks between PHP and other languages here (among other places);

http://benchmarksgame.alioth.debian.org/u32/compare.php?lang=gcc&lang2=php

igouy
  • 2,547
  • 17
  • 16
IvoTops
  • 3,463
  • 17
  • 18
0

You should use GD imagecopymerge instead of iterating on the pixels. And cache the results (bonus points if you use .htaccess-based redirection to avoid invoking PHP for cached images).

CAFxX
  • 28,060
  • 6
  • 41
  • 66