Image manipulation for cpg1.5.x Image manipulation for cpg1.5.x
 

News:

cpg1.5.48 Security release - upgrade mandatory!
The Coppermine development team is releasing a security update for Coppermine in order to counter a recently discovered vulnerability. It is important that all users who run version cpg1.5.46 or older update to this latest version as soon as possible.
[more]

Main Menu

Image manipulation for cpg1.5.x

Started by Timos-Welt, December 16, 2009, 02:23:34 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Timos-Welt

This plugin allows the visitor to non-destructively manipulate pictures using new buttons on displayimage.php. Depending on the plugin configuration, the settings for each picture may be saved in a cookie on the visitor's computer (so if a picture is visited again, the old settings will be applied), and the URL may change accordingly (so if the visitor passes on the link, the recepient will see the pic just as the visitor likes it). Check the demo to see what all this means. The plugin will add ~30 kbyte of javascript to the page (slightly less in compatible mode).

Available effects in all browsers including Internet Explorer 5.5-8:

  • Brightness (LED slider)
  • Reset
  • B/W
  • Invert
  • Emboss
  • Flip vertically
  • Flip horizontally
  • Blur

Available effects in all actual browsers but not in Microsoft's IE before version 9:

  • Contrast (LED slider)
  • Saturation (LED slider)
  • Sharpness (LED slider)
  • Sepia (replacing B/W)

Browser compatibility:
The effects work fine with

  • IE 5.5+
  • Opera 9.5+
  • Firefox 2+
  • Chrome 3+
  • most other browsers should work, too or fall back nicely

Please note:

  • This plugin will only work if 'Insert a transparent overlay to minimize image theft' is deactivated.
  • This plugin and the plugin 'mirror' may not be used at the same time.
  • The buttons will only be available for visitors with javascript turned on. Others won't notice a thing.
  • It is highly recommended to only use controls that are available in all browsers (including IE < 9). If you don't, it depends on the visitor's browser which effects will be available.
  • Use the config page to set up the plugin.
  • Read the documentation!

Demo

SVN

Download latest SVN snapshot (use at own risk)

Download: http://sourceforge.net/projects/coppermine/files/Plugins/1.5.x/cpg1.5.x_plugin_image-manipulation_v2.1.zip/download

This plugin uses the great Pixastic Javascript library by Jacob Seidelin (MIT License).

Workaround for transparent overlay
If you like to have a transparent overlay over your images and don't use the annotate plugin, you may paste this to your theme.php to get it back (leave the setting on config page turned off when using this workaround). Use at your own risk and test carefully, especially with other plugins you use at the same time:
/******************************************************************************
** Section <<<theme_html_picture>>> - START
******************************************************************************/
// Displays a picture
function theme_html_picture()
{
    global $CONFIG, $CURRENT_PIC_DATA, $CURRENT_ALBUM_DATA, $USER, $LINEBREAK;
    global $album, $lang_date, $template_display_media;
    global $lang_display_image_php, $lang_picinfo, $lang_common, $lang_errors;

    $superCage = Inspekt::makeSuperCage();

    $pid = $CURRENT_PIC_DATA['pid'];
    $pic_title = '';

    if (!isset($USER['liv']) || !is_array($USER['liv'])) {
        $USER['liv'] = array();
    }
    // Add 1 to hit counter
    if ((!USER_IS_ADMIN && $CONFIG['count_admin_hits'] == 0 || $CONFIG['count_admin_hits'] == 1) && !in_array($pid, $USER['liv']) && $superCage->cookie->keyExists($CONFIG['cookie_name'] . '_data')) {
        add_hit($pid);
        if (count($USER['liv']) > 4) array_shift($USER['liv']);
        array_push($USER['liv'], $pid);
    }

    // The weird comparision is because only picture_width is stored
    if ($CONFIG['thumb_use']=='ht' && $CURRENT_PIC_DATA['pheight'] > $CONFIG['picture_width'] ) {
        $condition = true;
    } elseif ($CONFIG['thumb_use']=='wd' && $CURRENT_PIC_DATA['pwidth'] > $CONFIG['picture_width']) {
        $condition = true;
    } elseif ($CONFIG['thumb_use']=='any' && max($CURRENT_PIC_DATA['pwidth'], $CURRENT_PIC_DATA['pheight']) > $CONFIG['picture_width']) {
        $condition = true;
        //thumb cropping
    } elseif ($CONFIG['thumb_use']=='ex' && max($CURRENT_PIC_DATA['pwidth'], $CURRENT_PIC_DATA['pheight']) > $CONFIG['picture_width']) {
        $condition = true;
    } else {
        $condition = false;
    }

    if ($CURRENT_PIC_DATA['title'] != '') {
        $pic_title .= $CURRENT_PIC_DATA['title'] . $LINEBREAK;
    }
    if ($CURRENT_PIC_DATA['caption'] != '') {
        $pic_title .= $CURRENT_PIC_DATA['caption'] . $LINEBREAK;
    }
    if ($CURRENT_PIC_DATA['keywords'] != '') {
        $pic_title .= $lang_common['keywords'] . ": " . $CURRENT_PIC_DATA['keywords'];
    }

    if (!$CURRENT_PIC_DATA['title'] && !$CURRENT_PIC_DATA['caption']) {
        template_extract_block($template_display_media, 'img_desc');
    } else {
        if (!$CURRENT_PIC_DATA['title']) {
            template_extract_block($template_display_media, 'title');
        }
        if (!$CURRENT_PIC_DATA['caption']) {
            template_extract_block($template_display_media, 'caption');
        }
    }

    $CURRENT_PIC_DATA['menu'] = html_picture_menu(); //((USER_ADMIN_MODE && $CURRENT_ALBUM_DATA['category'] == FIRST_USER_CAT + USER_ID) || ($CONFIG['users_can_edit_pics'] && $CURRENT_PIC_DATA['owner_id'] == USER_ID && USER_ID != 0) || GALLERY_ADMIN_MODE) ? html_picture_menu($pid) : '';

    $image_size = array();

    if ($CONFIG['make_intermediate'] && $condition ) {
        $picture_url = get_pic_url($CURRENT_PIC_DATA, 'normal');
    } else {
        $picture_url = get_pic_url($CURRENT_PIC_DATA, 'fullsize');
    }

    list($image_size['width'], $image_size['height'], , $image_size['geom']) = cpg_getimagesize(urldecode($picture_url));

    $pic_title = '';
    $mime_content = cpg_get_type($CURRENT_PIC_DATA['filename']);

    if ($mime_content['content']=='movie' || $mime_content['content']=='audio') {

        if ($CURRENT_PIC_DATA['pwidth']==0 || $CURRENT_PIC_DATA['pheight']==0) {
            $CURRENT_PIC_DATA['pwidth']  = 320; // Default width

            // Set default height; if file is a movie
            if ($mime_content['content']=='movie') {
                $CURRENT_PIC_DATA['pheight'] = 240; // Default height
            }
        }

        $ctrl_offset['mov']=15;
        $ctrl_offset['wmv']=45;
        $ctrl_offset['swf']=0;
        $ctrl_offset['rm']=0;
        $ctrl_offset_default=45;
        $ctrl_height = (isset($ctrl_offset[$mime_content['extension']]))?($ctrl_offset[$mime_content['extension']]):$ctrl_offset_default;
        $image_size['whole']='width="'.$CURRENT_PIC_DATA['pwidth'].'" height="'.($CURRENT_PIC_DATA['pheight']+$ctrl_height).'"';
    }

    if ($mime_content['content']=='image') {
        if ($CURRENT_PIC_DATA['mode'] != 'fullsize') {
            $winsizeX = $CURRENT_PIC_DATA['pwidth'] + $CONFIG['fullsize_padding_x'];  //the +'s are the mysterious FF and IE paddings
            $winsizeY = $CURRENT_PIC_DATA['pheight'] + $CONFIG['fullsize_padding_y']; //the +'s are the mysterious FF and IE paddings
                $pic_html_href_close = '</a>' . $LINEBREAK;
                $pic_html = "<div style=\"text-align:center;\">";
                $pic_html .= "<div style=\"position:relative;margin:auto auto;width:{$image_size['width']}px;height:{$image_size['height']}px;\">";
                $pic_html .= "<img src=\"" . $picture_url . "\" {$image_size['geom']} class=\"image\" border=\"0\" alt=\"\" />";
                if (!USER_ID && $CONFIG['allow_unlogged_access'] <= 2) {
                    if ($CONFIG['allow_user_registration'] == 0) {
                        $pic_html_href_close = '';
                    } else {
                        $pic_html .= '<a href="javascript:;" onclick="alert(\''.sprintf($lang_errors['login_needed'],'','','','').'\');">';
                    }
                } elseif (USER_ID && USER_ACCESS_LEVEL <= 2) {
                    $pic_html .= '<a href="javascript:;" onclick="alert(\''.sprintf($lang_errors['access_intermediate_only'],'','','','').'\');">';
                } else {
                    $pic_html .= "<a href=\"javascript:;\" onclick=\"MM_openBrWindow('displayimage.php?pid=$pid&amp;fullsize=1','" . uniqid(rand()) . "','scrollbars=yes,toolbar=no,status=no,resizable=yes,width=$winsizeX,height=$winsizeY')\">";
                }
                $pic_title = $lang_display_image_php['view_fs'] . $LINEBREAK . '==============' . $LINEBREAK . $pic_title;
                $pic_html .= "<img src=\"images/image.gif?id=".floor(rand()*1000+rand())."\" width=\"{$image_size['width']}\" height=\"{$image_size['height']}\"  border=\"0\" alt=\"{$lang_display_image_php['view_fs']}\" style=\"position:absolute;left:0px;top:0px;margin:3px;";
                if ($pic_html_href_close) $pic_html .= "cursor:pointer;";
                $pic_html .= "\" />";
                $pic_html .= $pic_html_href_close."</div></div><br />";
                //PLUGIN FILTER
                $pic_html = CPGPluginAPI::filter('html_image_reduced_overlay', $pic_html);

        } else {
                $pic_html = "<div style=\"text-align:center;\">";
                $pic_html .= "<div style=\"position:relative;margin:auto auto;width:{$image_size['width']}px;height:{$image_size['height']}px;\">";
                $pic_html .= "<img src=\"" . $picture_url . "\" {$image_size['geom']} class=\"image\" border=\"0\" alt=\"\" /><br />" . $LINEBREAK;
                $pic_html .= "<img src=\"images/image.gif?id=".floor(rand()*1000+rand())."\" width=\"{$image_size['width']}\" height=\"{$image_size['height']}\"  border=\"0\" alt=\"\" style=\"position:absolute;left:0px;top:0px;margin:3px;\" />";
                $pic_html .= "</div></div><br />";
                //PLUGIN FILTER
                $pic_html = CPGPluginAPI::filter('html_image_overlay', $pic_html);
        }
    } elseif ($mime_content['content']=='document') {
        $pic_thumb_url = get_pic_url($CURRENT_PIC_DATA,'thumb');
        $pic_html = "<a href=\"{$picture_url}\" target=\"_blank\" class=\"document_link\"><img src=\"".$pic_thumb_url."\" border=\"0\" class=\"image\" /></a><br />" . $LINEBREAK;
        //PLUGIN FILTER
        $pic_html = CPGPluginAPI::filter('html_document', $pic_html);
    } else {
        $autostart = ($CONFIG['media_autostart']) ? ('true'):('false');

        if ($mime_content['player'] == 'HTMLA') {
            $pic_html  = '<audio controls="true" src="' . $picture_url . '" autostart="' . $autostart . '"></audio>';
        } elseif ($mime_content['player'] == 'HTMLV') {
            $pic_html  = '<video controls="true" src="' . $picture_url . '" autostart="' . $autostart . '"' . $image_size['whole'] . '></video>';
        } else {

            $players['WMP'] = array('id' => 'MediaPlayer',
                                    'clsid' => 'classid="clsid:22D6F312-B0F6-11D0-94AB-0080C74C7E95" ',
                                    'codebase' => 'codebase="http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=5,1,52,701" ',
                                    'mime' => 'type="application/x-mplayer2" ',
                                   );
            $players['DIVX'] = array('id' => 'DivX',
                                    'clsid' => 'classid="clsid:67DABFBF-D0AB-41fa-9C46-CC0F21721616"',
                                    'codebase' => 'codebase="http://go.divx.com/plugin/DivXBrowserPlugin.cab"',
                                    'mime' => 'type="video/divx"'
                                   );
            $players['RMP'] = array('id' => 'RealPlayer',
                                    'clsid' => 'classid="clsid:CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA" ',
                                    'codebase' => '',
                                    'mime' => 'type="audio/x-pn-realaudio-plugin" '
                                   );
            $players['QT']  = array('id' => 'QuickTime',
                                    'clsid' => 'classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" ',
                                    'codebase' => 'codebase="http://www.apple.com/qtactivex/qtplugin.cab" ',
                                    'mime' => 'type="video/x-quicktime" '
                                   );
            $players['SWF'] = array('id' => 'SWFlash',
                                    'clsid' => ' classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" ',
                                    'codebase' => 'codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0" ',
                                    'mime' => 'type="application/x-shockwave-flash" '
                                   );
            $players['UNK'] = array('id' => 'DefaultPlayer',
                                    'clsid' => '',
                                    'codebase' => '',
                                    'mime' => ''
                                   );

            $player = $players[$mime_content['player']];

            if (!$player) {
                $player = 'UNK';
            }

            $pic_html  = '<object id="'.$player['id'].'" '.$player['classid'].$player['codebase'].$player['mime'].$image_size['whole'].'>';
            $pic_html .= "<param name=\"autostart\" value=\"$autostart\" /><param name=\"src\" value=\"". $picture_url . "\" />";
            $pic_html .= '</object><br />' . $LINEBREAK;
        }

        //PLUGIN FILTER
        $pic_html = CPGPluginAPI::filter('html_other_media', $pic_html);
    }

    $CURRENT_PIC_DATA['html'] = $pic_html;
    $CURRENT_PIC_DATA['header'] = '';
    $CURRENT_PIC_DATA['footer'] = '';

    $CURRENT_PIC_DATA = CPGPluginAPI::filter('file_data',$CURRENT_PIC_DATA);

    $params = array('{CELL_HEIGHT}' => '100',
        '{IMAGE}' => $CURRENT_PIC_DATA['header'].$CURRENT_PIC_DATA['html'].$CURRENT_PIC_DATA['footer'],
        '{ADMIN_MENU}' => $CURRENT_PIC_DATA['menu'],
        '{TITLE}' => bb_decode($CURRENT_PIC_DATA['title']),
        '{CAPTION}' => bb_decode($CURRENT_PIC_DATA['caption']),
        );

    return template_eval($template_display_media, $params);
}

/******************************************************************************
** Section <<<theme_html_picture>>> - END
******************************************************************************/

/******************************************************************************
** Section <<<theme_display_fullsize_pic>>> - START
******************************************************************************/
// Display the full size image
function theme_display_fullsize_pic()
{
    global $CONFIG, $THEME_DIR, $FORBIDDEN_SET, $LINEBREAK, $pid;
    global $lang_errors, $lang_fullsize_popup, $lang_charset;

    $superCage = Inspekt::makeSuperCage();

    if (!USER_ID && $CONFIG['allow_unlogged_access'] <= 2) {
        printf($lang_errors['login_needed'],'','','','');
        die();
    } elseif (USER_ID && USER_ACCESS_LEVEL <= 2) {
        printf($lang_errors['access_intermediate_only'],'','','','');
        die();
    }
    if ($superCage->get->keyExists('picfile')) {
        if (!GALLERY_ADMIN_MODE) {
            cpg_die(ERROR, $lang_errors['access_denied'], __FILE__, __LINE__);
        }
        //$picfile = $_GET['picfile'];
        //$picfile = $superCage->get->getPath('picfile'); // doesn't work with HTML entities
        $matches = $superCage->get->getMatched('picfile', '/^[0-9A-Za-z\/_.-]+$/');
        $picfile = $matches[0];
        $picname = $CONFIG['fullpath'] . $picfile;
        $imagesize = @getimagesize($picname);
        $imagedata = array('name' => $picfile, 'path' => path2url($picname), 'geometry' => $imagesize[3]);
    } elseif ($pid) {
        $sql = "SELECT filepath, filename, url_prefix, pwidth, pheight FROM {$CONFIG['TABLE_PICTURES']} AS p " . "WHERE pid='$pid' $FORBIDDEN_SET";
        $result = cpg_db_query($sql);
        if (!mysql_num_rows($result)) {
            cpg_die(ERROR, $lang_errors['non_exist_ap'], __FILE__, __LINE__);
        }
        $row = mysql_fetch_assoc($result);
        $pic_url = get_pic_url($row, 'fullsize');
        $geom = 'width="' . $row['pwidth'] . '" height="' . $row['pheight'] . '"';
        $imagedata = array('name' => $row['filename'], 'path' => $pic_url, 'geometry' => $geom);
    }
    if ((!USER_ID && $CONFIG['allow_unlogged_access'] <= 2) || (USER_ID && USER_ACCESS_LEVEL <= 2)) {
        // adjust the size of the window if we don't have to catter for a full-size pop-up, but only a text message
        $row['pwidth'] = 200;
        $row['pheight'] = 100;
    }

    $charset = ($CONFIG['charset'] == 'language file' ? $lang_charset : $CONFIG['charset']);
    $fullsize_html = <<<EOT
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
    <head>
        <meta http-equiv="content-type" content="text/html; charset=$charset" />
        <title>{$CONFIG['gallery_name']}: {$lang_fullsize_popup['click_to_close']}</title>
        <style type="text/css">
            body { margin: 0; padding: 0; background-color: gray; }
            img { margin:0; padding:0; border:0; }
            #content { margin:0 auto; padding:0; border:0; }
            table { border:0; width:{$row['pwidth']}px; height:{$row['pheight']}px; border-collapse:collapse}
            td { vertical-align: middle; text-align:center; }
        </style>

        <script type="text/javascript" src="js/jquery-1.3.2.js"></script>
        <script type="text/javascript" src="js/jquery.dimensions.pack.js"></script>
        <script type="text/javascript" src="js/displayimage.fullsize.js"></script>
    </head>
    <body style="margin:0px; padding:0px; background-color: gray;">

EOT;

        $fullsize_html .= <<<EOT
        <table cellpadding="0" cellspacing="0" align="center" style="padding:0px;">
            <tr>

EOT;
        $fullsize_html .=  '<td align="center" valign="middle" background="' . htmlspecialchars($imagedata['path']) . '" ' . $imagedata['geometry'] . ' class="image">';
        $fullsize_html .=  '<div id="content">';
        $fullsize_html .=  '<a href="javascript: window.close()" style="border:none"><img src="images/image.gif?id='
                . floor(rand()*1000+rand())
                . '&amp;fullsize=yes" '
                . $imagedata['geometry']
                . ' alt="'
                . htmlspecialchars($imagedata['name'])
                . '" title="'
                . htmlspecialchars($imagedata['name'])
                . $LINEBREAK . $lang_fullsize_popup['click_to_close']
                . '" /></a><br />' . $LINEBREAK;
        $fullsize_html .=  <<<EOT
                    </div>
                </td>
            </tr>
        </table>
  </body>
</html>

EOT;

    $fullsize_html = CPGPluginAPI::filter('fullsize_html', $fullsize_html);
    echo $fullsize_html;
}
/******************************************************************************
** Section <<<theme_display_fullsize_pic>>> - END
******************************************************************************/


Αndré

Nice plugin.

I suggest to move the 'Reset' button to the right, as people who just 'play' with the buttons will start from the left and nothing will happen ;)

Αndré

Quote from: Timos-Welt on December 16, 2009, 02:23:34 PM
Turn off 'add transparent overlay to avoid image theft' to make this plugin work.
This should automatically be done at plugin installation imo.

Αndré

Is there a reason why you only have built in these effects
Quote from: Timos-Welt on December 16, 2009, 02:23:34 PM

  • reset
  • blur
  • b/w
  • invert
  • emboss
  • flip vert (IE only)
  • flip hori (IE only)
  • lighten (not in IE)
  • darken (not in IE)
  • solarize (not in IE)
?

There are more available effects, that should be configurable by the admin:
Quote from: http://www.pixastic.com/lib/download/Blend
Blur
Blur Fast
Brightness/Contrast
Color Adjust
Color Histogram
Crop
Desaturate
Edge Detection
Edge Detection 2
Emboss
Flip
Flip Horizontally
Flip Vertically
Glow
Histogram
Hue/Saturation/Lightness
Invert
Laplace Edge Detection
Lighten
Mosaic
Noise
Posterize
Pointillize
Remove Noise
Resize
Rotate
Sepia
Sharpen
Solarize
Unsharp Mask

Timos-Welt

Just released 0.2 that fixes a lot of display bugs at cost of performance and enables Lighten/Darken buttons for IE, too.

Andre:
Most of the effects are not compatible with all browsers or versions. Flip should work in Firefox, too, but it doesn't ATM (that's why the buttons are only available in IE). I think I'll contact the developer about that issue.

What works everywhere is blur, b/w, invert, emboss, flip, lighten and darken. I think I'll remove solarize as soon as flip works with any browser.

Timos-Welt

Found the flip bug - v0.3 works identically in any modern browser.

Pascal YAP

WoW I'm speechless :-)))))) Amazing plugin Tim ;D

Timos-Welt

v0.4:
- fixed: buttons were visible for non-images
- improvement: toggle buttons show their state

Timos-Welt

Feedback from the developer of the Pixastic Javascript library Jacob Seidelin:

"Pixastic is currently licensed under the MPL but I plan to extend that to a MPL/GPL dual license when I get around to updating the necessary files/pages - so, there shouldn't be a problem with you bundling it with your plugin. Just make sure the copyright and license information for Pixastic is included as well (the commented part in the top)."

:)

Timos-Welt

v0.5 adds a slider for pic brightness

Αndré

Nice :) But I still miss at least the Sepia effect ;)

Timos-Welt

No problem to code, but where's the sense in implementing something that won't work with every mainstream browser? There's a Google project implementing canvas functions into IE - feel free to add it and test it out.

Αndré

We could determine by whitelist or blacklist to add buttons for effects that don't work in the user's browser.

Timos-Welt

Potential source for misunderstandings and frustration. User A visits your gallery and writes a comment 'have a look at the image in sepia, it's great'. User B visits your gallery using a different browser and can't find the button for the sepia effect... A perfect way to make support effort appear from nowhere.

The current implementation focuses on compatibility. IE 5.5+, FF 2+, Opera 9.5+, Chrome 3+ will all work the same way, so >90% of the visitors should see the gallery as it is meant to be seen, including some nice new controls to play with. Do you want to have jam on it, too?

Αndré

Okay I can see your point. But can't we add a config panel where the admin can choose which buttons he want to display (if the user's browser supports that effect) and add some explanation like "This effect works only on the following browsers: xyz. It's a potential source for misunderstandings and frustration if your users talk about the effects using different browsers."?

Timos-Welt

Ok, ok, v0.6 has a compatibility mode, and if you turn it off, it will give new effects in other browsers than IE (contrast, saturation, sharpness, sepia). It is not recommended to turn it off. See demo in IE or FF to see the difference.

Αndré


Timos-Welt

V0.7 adds dynamic URL manipulation, so if you find you preferred settings for a pic and pass the URL to someone else, he/she will see the pic as you liked it.

Timos-Welt

v0.8 adds cookie support, so if a visitor visits the same pic later, his settings will be remembered and applied automatically.

Timos-Welt

This plugin has finally reached v1.0 and is now a genuine cpg plugin. Have fun with it.

Localisations are welcome!