diff --git a/docs/upgrades/packages-7.7.1/image3.wgpkg b/docs/upgrades/packages-7.7.1/image3.wgpkg new file mode 100644 index 000000000..0b72d1e2a Binary files /dev/null and b/docs/upgrades/packages-7.7.1/image3.wgpkg differ diff --git a/docs/upgrades/upgrade_7.7.0-7.7.1.pl b/docs/upgrades/upgrade_7.7.0-7.7.1.pl index 6ddcce291..ade60ff83 100644 --- a/docs/upgrades/upgrade_7.7.0-7.7.1.pl +++ b/docs/upgrades/upgrade_7.7.0-7.7.1.pl @@ -31,10 +31,15 @@ my $quiet; # this line required my $session = start(); # this line required # upgrade functions go here - addWelcomeMessageTemplateToSettings( $session ); addStatisticsCacheTimeoutToMatrix( $session ); +# image mods +addImageAnnotation($session); + +# rss mods +addRssLimit($session); + finish($session); # this line required sub addWelcomeMessageTemplateToSettings { @@ -45,6 +50,28 @@ sub addWelcomeMessageTemplateToSettings { print "Done.\n" unless $quiet; } +#---------------------------------------------------------------------------- +sub addRssLimit { + my $session = shift; + print "\tAdding rssLimit to RSSCapable table, if needed... \n" unless $quiet; + my $sth = $session->db->read('describe RSSCapable rssCableRssLimit'); + if (! defined $sth->hashRef) { + $session->db->write("alter table RSSCapable add column rssCableRssLimit integer"); + } + print "Done.\n" unless $quiet; +} + +#---------------------------------------------------------------------------- +sub addImageAnnotation { + my $session = shift; + print "\tAdding annotations to imageAsset table, if needed... \n" unless $quiet; + my $sth = $session->db->read('describe imageAsset annotations'); + if (! defined $sth->hashRef) { + $session->db->write("alter table imageAsset add column annotations mediumtext"); + } + print "Done.\n" unless $quiet; +} + #---------------------------------------------------------------------------- sub addStatisticsCacheTimeoutToMatrix{ my $session = shift; diff --git a/lib/WebGUI/Asset.pm b/lib/WebGUI/Asset.pm index 585a84ccf..76729aec2 100644 --- a/lib/WebGUI/Asset.pm +++ b/lib/WebGUI/Asset.pm @@ -2606,6 +2606,11 @@ NOTE: Don't try to override or overload this method. It won't work. What you are sub www_editSave { my $self = shift; + + my $annotations = ""; + if ($self->isa("WebGUI::Asset::File::Image")) { + $annotations = $self->get("annotations"); + } ##If this is a new asset (www_add), the parent may be locked. We should still be able to add a new asset. my $isNewAsset = $self->session->form->process("assetId") eq "new" ? 1 : 0; return $self->session->privilege->locked() if (!$self->canEditIfLocked and !$isNewAsset); @@ -2644,6 +2649,12 @@ sub www_editSave { } } + if ($self->isa("WebGUI::Asset::File::Image")) { + $object->update({ annotations => $annotations }); + } + + ### + $object->updateHistory("edited"); # we handle auto commit assets here in case they didn't handle it themselves diff --git a/lib/WebGUI/Asset/File.pm b/lib/WebGUI/Asset/File.pm index 214f264c5..884ebadeb 100644 --- a/lib/WebGUI/Asset/File.pm +++ b/lib/WebGUI/Asset/File.pm @@ -245,7 +245,6 @@ sub getEditFormUploadControl { return $html; } - #------------------------------------------------------------------- sub getFileUrl { my $self = shift; @@ -559,10 +558,11 @@ sub www_edit { #------------------------------------------------------------------- + sub www_view { my $self = shift; return $self->session->privilege->noAccess() unless $self->canView; - + # Check to make sure it's not in the trash or some other weird place if ($self->get("state") ne "published") { my $i18n = WebGUI::International->new($self->session,'Asset_File'); diff --git a/lib/WebGUI/Asset/File/Image.pm b/lib/WebGUI/Asset/File/Image.pm index 9e639d746..22f471ee9 100644 --- a/lib/WebGUI/Asset/File/Image.pm +++ b/lib/WebGUI/Asset/File/Image.pm @@ -112,6 +112,10 @@ sub definition { fieldType => 'textarea', defaultValue => 'style="border-style:none;"', }, + annotations => { + fieldType => 'textarea', + defaultValue => '', + }, }, }; return $class->SUPER::definition($session,$definition); @@ -236,17 +240,32 @@ sub view { return $out if $out; } my %var = %{$self->get}; + my ($crop_js, $domMe) = $self->annotate_js({ just_image => 1 }); + + if ($crop_js) { + my ($style, $url) = $self->session->quick(qw(style url)); + + $style->setLink($url->extras('yui/build/fonts/fonts-min.css'), {rel=>'stylesheet', type=>'text/css'}); + $style->setLink($url->extras('yui/container/assets/container.css'), {rel=>'stylesheet', type=>'text/css'}); + $style->setScript($url->extras('yui/build/yahoo-dom-event/yahoo-dom-event.js'), {type=>'text/javascript'}); + $style->setScript($url->extras('yui/build/container/container-min.js'), {type=>'text/javascript'}); + } + $var{controls} = $self->getToolbar; $var{fileUrl} = $self->getFileUrl; $var{fileIcon} = $self->getFileIconUrl; $var{thumbnail} = $self->getThumbnailUrl; - my $out = $self->processTemplate(\%var,undef,$self->{_viewTemplate}); + $var{annotateJs} = "$crop_js$domMe"; + $var{parameters} = sprintf("id=%s", $self->getId()); + my $form = $self->session->form; + my $out = $self->processTemplate(\%var,undef,$self->{_viewTemplate}); if (!$self->session->var->isAdminOn && $self->get("cacheTimeout") > 10) { WebGUI::Cache->new($self->session,"view_".$self->getId)->set($out,$self->get("cacheTimeout")); } return $out; } + #---------------------------------------------------------------------------- =head2 setFile ( filename ) @@ -268,6 +287,10 @@ sub www_edit { return $self->session->privilege->locked() unless $self->canEditIfLocked; my $i18n = WebGUI::International->new($self->session, 'Asset_Image'); $self->getAdminConsole->addSubmenuItem($self->getUrl('func=resize'),$i18n->get("resize image")) if ($self->get("filename")); + $self->getAdminConsole->addSubmenuItem($self->getUrl('func=rotate'),$i18n->get("rotate image")) if ($self->get("filename")); + $self->getAdminConsole->addSubmenuItem($self->getUrl('func=crop'),$i18n->get("crop image")) if ($self->get("filename")); + $self->getAdminConsole->addSubmenuItem($self->getUrl('func=annotate'),$i18n->get("annotate image")) if ($self->get("filename")); + $self->getAdminConsole->addSubmenuItem($self->getUrl('func=undo'),$i18n->get("undo image")) if ($self->get("filename")); my $tabform = $self->getEditForm; $tabform->getTab("display")->template( -value=>$self->get("templateId"), @@ -278,6 +301,283 @@ sub www_edit { return $self->getAdminConsole->render($tabform->print,$i18n->get("edit image")); } +#------------------------------------------------------------------- +sub www_undo { + my $self = shift; + my $previous = (@{$self->getRevisions()})[1]; + # instantiate assetId + if ($previous) { + # my $session = $self->session; + + # my $cache = WebGUI::Cache->new($self->session, ["asset",$self->getId,$self->getRevisionDate]); + # $cache->flush; + # my $cache = WebGUI::Cache->new($previous->session, ["asset",$previous->getId,$previous->getRevisionDate]); + # $cache->flush; + + $self = $self->purgeRevision(); + # $self = undef; + # $self = WebGUI::Asset->new($previous->session, $previous->getId, ref $previous, $previous->getRevisionDate); + $self = $previous; + $self->generateThumbnail; + } + return $self->www_edit(); +} + +#------------------------------------------------------------------- + +# +# All of the images will have to change to support annotate. +# The revision system doesn't support the blobs, it seems. +# All of the image operations will have to be updated to support annotations. +# + +sub www_annotate { + my $self = shift; + return $self->session->privilege->insufficient() unless $self->canEdit; + return $self->session->privilege->locked() unless $self->canEditIfLocked; + if (1) { + my $newSelf = $self->addRevision(); + delete $newSelf->{_storageLocation}; + $newSelf->getStorageLocation->annotate($newSelf->get("filename"),$newSelf,$newSelf->session->form); + $newSelf->setSize($newSelf->getStorageLocation->getFileSize($newSelf->get("filename"))); + $self = $newSelf; + $self->generateThumbnail; + } + + my ($style, $url) = $self->session->quick(qw(style url)); + # $style->setLink($url->extras('annotate/imageMap.css'), {rel=>'stylesheet', type=>'text/css'}); + + $style->setLink($url->extras('yui/build/resize/assets/skins/sam/resize.css'), {rel=>'stylesheet', type=>'text/css'}); + $style->setLink($url->extras('yui/build/fonts/fonts-min.css'), {rel=>'stylesheet', type=>'text/css'}); + $style->setLink($url->extras('yui/build/imagecropper/assets/skins/sam/imagecropper.css'), {rel=>'stylesheet', type=>'text/css'}); + + $style->setScript($url->extras('yui/build/yahoo-dom-event/yahoo-dom-event.js'), {type=>'text/javascript'}); + $style->setScript($url->extras('yui/build/element/element-beta-min.js'), {type=>'text/javascript'}); + $style->setScript($url->extras('yui/build/dragdrop/dragdrop-min.js'), {type=>'text/javascript'}); + $style->setScript($url->extras('yui/build/resize/resize-min.js'), {type=>'text/javascript'}); + $style->setScript($url->extras('yui/build/imagecropper/imagecropper-beta-min.js'), {type=>'text/javascript'}); + + # my $imageAsset = $self->session->db->getRow("ImageAsset","assetId",$self->getId); + + my @pieces = split(/\n/, $self->get('annotations')); + # my ($top_left, $width_height, $note) = split(/\n/, $imageAsset->{annotations}); + + my ($img_null, $tooltip_block, $tooltip_none) = ('', '', ''); + for (my $i = 0; $i < $#pieces; $i += 3) { + $img_null .= "YAHOO.img.container.tt$i = null;\n"; + $tooltip_block .= "YAHOO.util.Dom.setStyle('tooltip$i', 'display', 'block');\n"; + $tooltip_none .= "YAHOO.util.Dom.setStyle('tooltip$i', 'display', 'none');\n"; + my $j = $i + 2; + # warn("i: $i: ", $self->session->form->process("delAnnotate$i")); + } + + my $image = '
'.$self->get(
'; + + my ($width, $height) = $self->getStorageLocation->getSize($self->get("filename")); + + my @checkboxes = (); + my $i18n = WebGUI::International->new($self->session,"Asset_Image"); + my $f = WebGUI::HTMLForm->new($self->session,-action=>$self->getUrl); + + $self->getAdminConsole->addSubmenuItem($self->getUrl('func=edit'),$i18n->get("edit image")); + $f->hidden( + -name=>"func", + -value=>"annotate" + ); + $f->text( + -label=>$i18n->get('annotate image'), + -value=>'', + -hoverHelp=>$i18n->get('annotate image description'), + -name=>'annotate_text' + ); + $f->integer( + -label=>$i18n->get('top'), + -name=>"annotate_top", + -value=>, + ); + $f->integer( + -label=>$i18n->get('left'), + -name=>"annotate_left", + -value=>, + ); + $f->integer( + -label=>$i18n->get('width'), + -name=>"annotate_width", + -value=>, + ); + $f->integer( + -label=>$i18n->get('height'), + -name=>"annotate_height", + -value=>, + ); + $f->button( + -value=>$i18n->get('annotate'), + -extras=>'onclick="switchState();"', + ); + $f->submit; + my ($crop_js, $domMe) = $self->annotate_js(); + return $self->getAdminConsole->render($f->print."$image$crop_js$domMe",$i18n->get("annotate image")); +} + +#------------------------------------------------------------------- +sub annotate_js { + my $self = shift; + my $opts = shift; + + my @pieces = split(/\n/, $self->get('annotations')); + + # warn("pieces: $#pieces: ". $self->getId()); + return "" if !@pieces && $opts->{just_image}; + + my ($img_null, $tooltip_block, $tooltip_none) = ('', '', ''); + for (my $i = 0; $i < $#pieces; $i += 3) { + $img_null .= "YAHOO.img.container.tt$i = null;\n"; + $tooltip_block .= "YAHOO.util.Dom.setStyle('tooltip$i', 'display', 'block');\n"; + $tooltip_none .= "YAHOO.util.Dom.setStyle('tooltip$i', 'display', 'none');\n"; + my $j = $i + 2; + # warn("i: $i: ", $self->session->form->process("delAnnotate$i")); + } + + my $id = $$opts{just_image} ? $self->getId : "yui_img"; + + my $crop_js = qq( + + ); + + my $hotspots = ''; + my $domMe = ''; + + for (my $i = 0; $i < $#pieces; $i += 3) { + my $top_left = $pieces[$i]; + my $width_height = $pieces[$i + 1]; + my $note = $pieces[$i + 2]; + + if ($top_left =~ /top: (\d+)px; left: (\d+)px;/) { + $top_left = "xy[0]+$1, xy[1]+$2"; + } + + my ($width, $height) = ("", ""); + if ($width_height =~ /width: (\d+)px; height: (\d+)px;/) { + ($width, $height) = ("$1px", "$2px"); + } + + # next if 3 == $i; + + warn('next'); + $domMe .= qq( + + + + + + + ); + } + + return($crop_js, $domMe); +} + +#------------------------------------------------------------------- +sub www_rotate { + my $self = shift; + return $self->session->privilege->insufficient() unless $self->canEdit; + return $self->session->privilege->locked() unless $self->canEditIfLocked; + # warn(sprintf("Rotate_formId: %s", $self->session->form->process("Rotate"))); + if (defined $self->session->form->process("Rotate")) { + my $newSelf = $self->addRevision(); + delete $newSelf->{_storageLocation}; + $newSelf->getStorageLocation->rotate($newSelf->get("filename"),$newSelf->session->form->process("Rotate")); + $newSelf->setSize($newSelf->getStorageLocation->getFileSize($newSelf->get("filename"))); + $self = $newSelf; + $self->generateThumbnail; + } + + my ($x, $y) = $self->getStorageLocation->getSizeInPixels($self->get("filename")); + + ##YUI specific datatable CSS + my ($style, $url) = $self->session->quick(qw(style url)); + + my $img_name = $self->getStorageLocation->getUrl($self->get("filename")); + my $img_file = $self->get("filename"); + my $image = '
'.$self->get(
'; + + my $i18n = WebGUI::International->new($self->session,"Asset_Image"); + $self->getAdminConsole->addSubmenuItem($self->getUrl('func=edit'),$i18n->get("edit image")); + my $f = WebGUI::HTMLForm->new($self->session,-action=>$self->getUrl); + $f->hidden( + -name=>"func", + -value=>"rotate" + ); + $f->button( + -value=>"Left", + -extras=>qq(onclick="var deg = document.getElementById('Rotate_formId').value; deg = parseInt(deg) + 90; document.getElementById('Rotate_formId').value = deg;"), + ); + $f->button( + -value=>"Right", + -extras=>qq(onclick="var deg = document.getElementById('Rotate_formId').value; deg = parseInt(deg) - 90; document.getElementById('Rotate_formId').value = deg;"), + ); + $f->integer( + -label=>$i18n->get('degree'), + -name=>"Rotate", + -value=>0, + ); + $f->submit; + return $self->getAdminConsole->render($f->print.$image,$i18n->get("rotate image")); +} + #------------------------------------------------------------------- sub www_resize { my $self = shift; @@ -289,7 +589,59 @@ sub www_resize { $newSelf->getStorageLocation->resize($newSelf->get("filename"),$newSelf->session->form->process("newWidth"),$newSelf->session->form->process("newHeight")); $newSelf->setSize($newSelf->getStorageLocation->getFileSize($newSelf->get("filename"))); $self = $newSelf; + $self->generateThumbnail; } + + my ($x, $y) = $self->getStorageLocation->getSizeInPixels($self->get("filename")); + + ##YUI specific datatable CSS + my ($style, $url) = $self->session->quick(qw(style url)); + + $style->setLink($url->extras('yui/build/fonts/fonts-min.css'), {rel=>'stylesheet', type=>'text/css'}); + $style->setLink($url->extras('yui/build/resize/assets/skins/sam/resize.css'), {rel=>'stylesheet', type=>'text/css'}); + $style->setScript($url->extras('yui/build/yahoo-dom-event/yahoo-dom-event.js'), {type=>'text/javascript'}); + $style->setScript($url->extras('yui/build/element/element-beta-min.js'), {type=>'text/javascript'}); + $style->setScript($url->extras('yui/build/dragdrop/dragdrop-min.js'), {type=>'text/javascript'}); + $style->setScript($url->extras('yui/build/resize/resize-min.js'), {type=>'text/javascript'}); + $style->setScript($url->extras('yui/build/animation/animation-min.js'), {type=>'text/javascript'}); + + my $resize_js = qq( + + ); + my $i18n = WebGUI::International->new($self->session,"Asset_Image"); $self->getAdminConsole->addSubmenuItem($self->getUrl('func=edit'),$i18n->get("edit image")); my $f = WebGUI::HTMLForm->new($self->session,-action=>$self->getUrl); @@ -297,7 +649,6 @@ sub www_resize { -name=>"func", -value=>"resize" ); - my ($x, $y) = $self->getStorageLocation->getSizeInPixels($self->get("filename")); $f->readOnly( -label=>$i18n->get('image size'), -hoverHelp=>$i18n->get('image size description'), @@ -316,15 +667,121 @@ sub www_resize { -value=>$y, ); $f->submit; - my $image = '
'.$self->get(
'; + my $image = '
'.$self->get(
'.$resize_js; return $self->getAdminConsole->render($f->print.$image,$i18n->get("resize image")); } +#------------------------------------------------------------------- +sub www_crop { + my $self = shift; + return $self->session->privilege->insufficient() unless $self->canEdit; + return $self->session->privilege->locked() unless $self->canEditIfLocked; + + if ($self->session->form->process("Width") || $self->session->form->process("Height") + || $self->session->form->process("Top") || $self->session->form->process("Left")) { + my $newSelf = $self->addRevision(); + delete $newSelf->{_storageLocation}; + $newSelf->getStorageLocation->crop( + $newSelf->get("filename"), + $newSelf->session->form->process("Width"), + $newSelf->session->form->process("Height"), + $newSelf->session->form->process("Top"), + $newSelf->session->form->process("Left") + ); + $self = $newSelf; + $self->generateThumbnail; + } + + my $filename = $self->get("filename"); + + ##YUI specific datatable CSS + my ($style, $url) = $self->session->quick(qw(style url)); + + my $crop_js = qq( + + ); + + $style->setLink($url->extras('yui/build/resize/assets/skins/sam/resize.css'), {rel=>'stylesheet', type=>'text/css'}); + $style->setLink($url->extras('yui/build/fonts/fonts-min.css'), {rel=>'stylesheet', type=>'text/css'}); + $style->setLink($url->extras('yui/build/imagecropper/assets/skins/sam/imagecropper.css'), {rel=>'stylesheet', type=>'text/css'}); + $style->setScript($url->extras('yui/build/yahoo-dom-event/yahoo-dom-event.js'), {type=>'text/javascript'}); + $style->setScript($url->extras('yui/build/element/element-beta-min.js'), {type=>'text/javascript'}); + $style->setScript($url->extras('yui/build/dragdrop/dragdrop-min.js'), {type=>'text/javascript'}); + $style->setScript($url->extras('yui/build/resize/resize-min.js'), {type=>'text/javascript'}); + $style->setScript($url->extras('yui/build/imagecropper/imagecropper-beta-min.js'), {type=>'text/javascript'}); + + my $i18n = WebGUI::International->new($self->session,"Asset_Image"); + + $self->getAdminConsole->addSubmenuItem($self->getUrl('func=edit'),$i18n->get("edit image")); + my $f = WebGUI::HTMLForm->new($self->session,-action=>$self->getUrl); + $f->hidden( + -name=>"degree", + -value=>"0" + ); + $f->hidden( + -name=>"func", + -value=>"crop" + ); + my ($x, $y) = $self->getStorageLocation->getSizeInPixels($filename); + $f->integer( + -label=>$i18n->get('width'), + -hoverHelp=>$i18n->get('new width description'), + -name=>"Width", + -value=>$x, + ); + $f->integer( + -label=>$i18n->get('height'), + -hoverHelp=>$i18n->get('new height description'), + -name=>"Height", + -value=>$y, + ); + $f->integer( + -label=>$i18n->get('top'), + -hoverHelp=>$i18n->get('new width description'), + -name=>"Top", + -value=>$x, + ); + $f->integer( + -label=>$i18n->get('left'), + -hoverHelp=>$i18n->get('new height description'), + -name=>"Left", + -value=>$y, + ); + $f->submit; + + my $image = '
'.$filename.'
'.$crop_js; + + return $self->getAdminConsole->render($f->print.$image,$i18n->get("crop image")); +} + #------------------------------------------------------------------- # Use superclass method for now. sub www_view { my $self = shift; - $self->SUPER::www_view; + return($self->SUPER::www_view); } #sub www_view { diff --git a/lib/WebGUI/Asset/Wobject/Collaboration.pm b/lib/WebGUI/Asset/Wobject/Collaboration.pm index db0b0a6cd..da641d788 100644 --- a/lib/WebGUI/Asset/Wobject/Collaboration.pm +++ b/lib/WebGUI/Asset/Wobject/Collaboration.pm @@ -861,6 +861,7 @@ SQL my $datetime = $self->session->datetime; my @posts; + my $rssLimit = $self->get('rssCapableRssLimit') || 10; for my $postId (@postIds) { my $post = WebGUI::Asset->new($self->session, $postId, 'WebGUI::Asset::Post::Thread'); my $postUrl = $siteUrl . $post->getUrl; @@ -896,6 +897,8 @@ SQL userDefined4 => $post->get("userDefined4"), userDefined5 => $post->get("userDefined5"), }; + + last if $rssLimit <= scalar(@posts); } return @posts; diff --git a/lib/WebGUI/Storage.pm b/lib/WebGUI/Storage.pm index 6432e33ac..af39508a0 100644 --- a/lib/WebGUI/Storage.pm +++ b/lib/WebGUI/Storage.pm @@ -678,6 +678,32 @@ sub generateThumbnail { #------------------------------------------------------------------- +=head2 getSize ( filename ) + +Returns width and height of image. + +=head3 filename + +The file to generate a thumbnail for. + +=cut + +sub getSize { + my $self = shift; + my $filename = shift; + my $image = Image::Magick->new; + my $error = $image->Read($self->getPath($filename)); + if ($error) { + $self->session->errorHandler->error("Couldn't read image for size reading: ".$error); + return 0; + } + my ($x, $y) = $image->Get('width','height'); + + return($x, $y); +} + +#------------------------------------------------------------------- + =head2 getErrorCount ( ) Returns the number of errors that have been generated on this object instance. @@ -1054,6 +1080,203 @@ sub renameFile { #------------------------------------------------------------------- +=head2 crop ( filename [, width, height ] ) + +Resizes the specified image by the specified height and width. If either is omitted the iamge will be scaleed proportionately to the non-omitted one. + +=head3 filename + +The name of the file to resize. + +=head3 width + +The new width of the image in pixels. + +=head3 height + +The new height of the image in pixels. + +=head3 x + +The top of the image in pixels. + +=head3 y + +The top of the image in pixels. + +=cut + +# TODO: Make this take a hash reference with width, height, and density keys. + +sub crop { + my $self = shift; + my $filename = shift; + my $width = shift; + my $height = shift; + my $x = shift; + my $y = shift; + unless (defined $filename) { + $self->session->errorHandler->error("Can't resize when you haven't specified a file."); + return 0; + } + unless ($self->isImage($filename)) { + $self->session->errorHandler->error("Can't resize something that's not an image."); + return 0; + } + unless ($width || $height || $x || $y) { + $self->session->errorHandler->error("Can't resize with no resizing parameters."); + return 0; + } + my $image = Image::Magick->new; + my $error = $image->Read($self->getPath($filename)); + if ($error) { + $self->session->errorHandler->error("Couldn't read image for resizing: ".$error); + return 0; + } + + # Next, resize dimensions + if ( $width || $height || $x || $y ) { + $self->session->errorHandler->info( "Resizing $filename to w:$width h:$height x:$x y:$y" ); + $image->Crop( height => $height, width => $width, x => $x, y => $y ); + } + + # Write our changes to disk + $error = $image->Write($self->getPath($filename)); + if ($error) { + $self->session->errorHandler->error("Couldn't resize image: ".$error); + return 0; + } + + return 1; +} + +#------------------------------------------------------------------- + +=head2 annotate ( filename [ text ] ) + +Adds annotation text to the image. + +=head3 filename + +The name of the file to annotate. + +=head3 text + +Text to add. + +=cut + +sub annotate { + my $self = shift; + my $filename = shift; + my $asset = shift; + my $form = shift; + unless (defined $filename) { + $self->session->errorHandler->error("Can't rotate when you haven't specified a file."); + return 0; + } + unless ($self->isImage($filename)) { + $self->session->errorHandler->error("Can't rotate something that's not an image."); + return 0; + } + # unless ($annotate_text) { + # $self->session->errorHandler->error("Can't annotate with no text."); + # return 0; + # } + # unless ($annotate_top && $annotate_left && $annotate_width && $annotate_height) { + # $self->session->errorHandler->error("Can't annotate with no dimensions."); + # return 0; + # } + + my $annotate = $asset->get('annotations'); + my $save_annotate = ""; + my @pieces = split(/\n/, $annotate); + for (my $i = 0; $i < $#pieces; $i += 3) { + my $top_left = $pieces[$i]; + my $width_height = $pieces[$i + 1]; + my $note = $pieces[$i + 2]; + + # warn("i: $i: ", $form->process("delAnnotate$i")); + next if $form->process("delAnnotate$i"); + + if ($save_annotate) { + $save_annotate .= "\n"; + } + $save_annotate .= "$top_left\n$width_height\n$note"; + } + + my $annotate_text = $form->process("annotate_text"); + my $annotate_top = $form->process("annotate_top"); + my $annotate_left = $form->process("annotate_left"); + my $annotate_width = $form->process("annotate_width"); + my $annotate_height = $form->process("annotate_height"); + # warn(qq(unless ($annotate_top && $annotate_left && $annotate_width && $annotate_height && $annotate_text !~ /^\s*$/))); + if (defined $annotate_top && defined $annotate_left && defined $annotate_width && defined $annotate_height && $annotate_text !~ /^\s*$/) { + if ($save_annotate) { + $save_annotate .= "\n"; + } + # warn(qq($save_annotate .= "top: ${annotate_top}px; left: ${annotate_left}px;\nwidth: ${annotate_width}px; height: ${annotate_height}px;\n'$annotate_text'")); + $save_annotate .= "top: ${annotate_top}px; left: ${annotate_left}px;\nwidth: ${annotate_width}px; height: ${annotate_height}px;\n$annotate_text"; + } + # warn($save_annotate); + + $asset->update({ annotations => $save_annotate }); + $save_annotate = $asset->get('annotations'); + # warn($save_annotate); + + return 1; +} + +#------------------------------------------------------------------- + +=head2 rotate ( filename [ degrees ] ) + +Rotates the image by the specified degrees. + +=head3 filename + +The name of the file to resize. + +=head3 width + +Number of degrees to rotate. + +=cut + +sub rotate { + my $self = shift; + my $filename = shift; + my $degree = shift || 0; + unless (defined $filename) { + $self->session->errorHandler->error("Can't rotate when you haven't specified a file."); + return 0; + } + unless ($self->isImage($filename)) { + $self->session->errorHandler->error("Can't rotate something that's not an image."); + return 0; + } + my $image = Image::Magick->new; + my $error = $image->Read($self->getPath($filename)); + if ($error) { + $self->session->errorHandler->error("Couldn't read image for resizing: ".$error); + return 0; + } + + $self->session->errorHandler->info( "Rotating $filename by $degree degrees" ); + $image->Rotate( $degree ); + + # Write our changes to disk + $error = $image->Write($self->getPath($filename)); + if ($error) { + $self->session->errorHandler->error("Couldn't rotate image: ".$error); + return 0; + } + + return 1; +} + +#------------------------------------------------------------------- + =head2 resize ( filename [, width, height ] ) Resizes the specified image by the specified height and width. If either is omitted the iamge will be scaleed proportionately to the non-omitted one. diff --git a/lib/WebGUI/i18n/English/Asset_Image.pm b/lib/WebGUI/i18n/English/Asset_Image.pm index 026b1529d..6ed0c5b2f 100644 --- a/lib/WebGUI/i18n/English/Asset_Image.pm +++ b/lib/WebGUI/i18n/English/Asset_Image.pm @@ -77,6 +77,54 @@ shown here.|, lastUpdated => 1106765841 }, + 'annotate' => { + message => q|Annotate|, + context => q|label to annotate the image|, + lastUpdated => 1106765841 + }, + + 'annotate image' => { + message => q|Annotate Image|, + context => q|label to annotate the image|, + lastUpdated => 1106765841 + }, + + 'annotate image description' => { + message => q|Text Around the Image|, + context => q|label to annotate the image|, + lastUpdated => 1106765841 + }, + + 'degree' => { + message => q|Degrees to Rotate|, + context => q|label to rotate the image|, + lastUpdated => 1106765841 + }, + + 'rotate image label' => { + message => q|Please click to rotate image|, + context => q|label to rotate the image|, + lastUpdated => 1106765841 + }, + + 'rotate image' => { + message => q|Rotate Image|, + context => q|label to rotate the image|, + lastUpdated => 1106765841 + }, + + 'rotate image label' => { + message => q|Please click to rotate image|, + context => q|label to rotate the image|, + lastUpdated => 1106765841 + }, + + 'crop image' => { + message => q|Crop Image|, + context => q|label to crop the image|, + lastUpdated => 1106765841 + }, + 'new width' => { message => q|New Width|, context => q|label to resize the image|, @@ -101,6 +149,42 @@ shown here.|, lastUpdated => 1130538987 }, + 'undo image' => { + message => q|Undo Image|, + context => q|undo editing operations|, + lastUpdated => 1106765841 + }, + + 'delete' => { + message => q|Delete|, + context => q|label to delete annotation|, + lastUpdated => 1106765841 + }, + + 'height' => { + message => q|Height|, + context => q|label to resize the image|, + lastUpdated => 1106765841 + }, + + 'width' => { + message => q|Width|, + context => q|label to resize the image|, + lastUpdated => 1106765841 + }, + + 'top' => { + message => q|Top|, + context => q|label to resize the image|, + lastUpdated => 1106765841 + }, + + 'left' => { + message => q|Left|, + context => q|label to resize the image|, + lastUpdated => 1106765841 + }, + 'image template title' => { message => q|Image Template Variables|, lastUpdated => 1184820779, diff --git a/t/Asset/File/Image.t b/t/Asset/File/Image.t index bad2c80d4..90307548d 100644 --- a/t/Asset/File/Image.t +++ b/t/Asset/File/Image.t @@ -20,6 +20,7 @@ BEGIN { $mocker->fake_new('WebGUI::Form::Image'); } +use File::Copy; use WebGUI::Test; use WebGUI::Session; use WebGUI::Image; @@ -29,23 +30,23 @@ use WebGUI::Form::File; use Test::More; # increment this value for each test you create use Test::Deep; -plan tests => 7; +plan tests => 11; my $session = WebGUI::Test->session; -my $square = WebGUI::Image->new($session, 100, 100); -$square->setBackgroundColor('#0000FF'); +my $rectangle = WebGUI::Image->new($session, 100, 200); +$rectangle->setBackgroundColor('#0000FF'); ##Create a storage location my $storage = WebGUI::Storage->create($session); ##Save the image to the location -$square->saveToStorageLocation($storage, 'square.png'); +$rectangle->saveToStorageLocation($storage, 'blue.png'); ##Do a file existance check. ok((-e $storage->getPath and -d $storage->getPath), 'Storage location created and is a directory'); -cmp_bag($storage->getFiles, ['square.png'], 'Only 1 file in storage with correct name'); +cmp_bag($storage->getFiles, ['blue.png'], 'Only 1 file in storage with correct name'); ##Initialize an Image Asset with that filename and storage location @@ -68,9 +69,29 @@ is($asset->get('storageId'), $asset->getStorageLocation->getId, 'Image Asset sto $asset->update({ storageId => $storage->getId, - filename => 'square.png', + filename => 'blue.png', }); +my $filename = $asset->getStorageLocation->getPath . "/" . $asset->get("filename"); + +my @stat_before = stat($filename); +$asset->getStorageLocation->rotate($asset->get("filename"), 90); +my @stat_after = stat($filename); +is(isnt_array(\@stat_before, \@stat_after), 1, 'Image is different after rotation'); + +@stat_before = stat($filename); +$asset->getStorageLocation->resize($asset->get("filename"), 200, 300); +my @stat_after = stat($filename); +is(isnt_array(\@stat_before, \@stat_after), 1, 'Image is different after resize'); + +@stat_before = stat($filename); +$asset->getStorageLocation->crop($asset->get("filename"), 100, 125, 10, 25); +my @stat_after = stat($filename); +is(isnt_array(\@stat_before, \@stat_after), 1, 'Image is different after crop'); + +my $sth = $session->db->read('describe imageAsset annotations'); +isnt($sth->hashRef, undef, 'Annotations column is defined'); + is($storage->getId, $asset->get('storageId'), 'Asset updated with correct new storageId'); is($storage->getId, $asset->getStorageLocation->getId, 'Cached Asset storage location updated with correct new storageId'); @@ -81,3 +102,13 @@ END { $versionTag->rollback; } } + +sub isnt_array { + my ($a, $b) = @_; + + for (my $i = 0; $i < @{ $a }; ++$i) { + return 1 if @{ $a }[$i] ne @{ $b }[$i]; + } + + return 0; +} diff --git a/www/extras/yui/build/fonts/imagecropper/assets/sam/imagecropper.css b/www/extras/yui/build/fonts/imagecropper/assets/sam/imagecropper.css new file mode 100644 index 000000000..73d3216f9 --- /dev/null +++ b/www/extras/yui/build/fonts/imagecropper/assets/sam/imagecropper.css @@ -0,0 +1,7 @@ +/* +Copyright (c) 2008, Yahoo! Inc. All rights reserved. +Code licensed under the BSD License: +http://developer.yahoo.net/yui/license.txt +version: 2.6.0 +*/ +.yui-crop{position:relative;}.yui-crop .yui-crop-mask{position:absolute;top:0;left:0;height:100%;width:100%;}.yui-crop .yui-resize{position:absolute;top:10px;left:10px;border:0;}.yui-crop .yui-crop-resize-mask{position:absolute;top:0;left:0;height:100%;width:100%;background-position:-10px -10px;overflow:hidden;}.yui-skin-sam .yui-crop .yui-crop-mask{background-color:#000;opacity:.5;filter:alpha(opacity=50);}.yui-skin-sam .yui-crop .yui-resize{border:1px dashed #fff;}