To participate you must create an account on apostrophenow.org. If you have already done so, click Login.

Show
Ignore:
Timestamp:
08/08/10 12:18:34 (22 months ago)
Author:
tboutell
Message:

Merged the cropping branch back to trunk:

Cropping is now available in the media repository. Every time you select an image for use on the site you have the opportunity to crop it. The image size constraints of the slot are taken into account. This means that you can satisfy a given set of constraints by cropping a wide variety of images that would not have been eligible for selection before.

Crops of existing images appear in the database as separate aMediaItem objects but do not duplicate the original image file. This means that many crops of a single source image can exist without significant overhead.

The slug field of a cropped version of an image contains cropping parameters. When rendering the image, Symfony routes spot these and crop the original file as needed.

By approaching the problem this way we have avoided the need for code that takes advantage of the media repository's image selection capabilities to change in any way in order to take advantage of cropping.

The single-image-select behavior of Apostrophe has changed to accommodate the extra cropping step. Single select for PDFs and videos still uses the selectSingle partial because they cannot be cropped.

Thanks to Spike Broehm for his contributions to the cropping project.

Fixes #227

Location:
plugins/apostrophePlugin/trunk
Files:
2 modified

Legend:

Unmodified
Added
Removed
  • plugins/apostrophePlugin/trunk

  • plugins/apostrophePlugin/trunk/lib/action/BaseaMediaActions.class.php

    r1872 r1917  
    3131        } 
    3232 
     33  // Supported for backwards compatibility. See also  
     34  // aMediaSelect::select() 
     35   
    3336  public function executeSelect(sfRequest $request) 
    3437  { 
     
    4548      $selection = array($request->getParameter('aMediaId') + 0); 
    4649    }  
    47     $items = aMediaItemTable::retrieveByIds($selection); 
    48     $ids = array(); 
    49     foreach ($items as $item) 
    50     { 
    51       $ids[] = $item->getId(); 
    52     } 
    5350    $options = array(); 
    5451    $optional = array('type', 'aspect-width', 'aspect-height', 
     
    6158      } 
    6259    } 
    63     aMediaTools::setSelecting($after, $multiple, $ids, $options); 
     60    aMediaTools::setSelecting($after, $multiple, $selection, $options); 
    6461       
    6562    return $this->redirect("aMedia/index"); 
     
    113110    $aspectWidth = floor(aMediaTools::getAttribute('aspect-width')); 
    114111    $aspectHeight = floor(aMediaTools::getAttribute('aspect-height')); 
    115     // TODO: performance of these is not awesome (it's a linear search).  
    116     // It would be more awesome with the right kind of indexing. For the  
    117     // aspect ratio test to be more efficient we'd have to store the lowest  
    118     // common denominator aspect ratio and index that. 
    119     if ($aspectWidth && $aspectHeight) 
    120     { 
    121       $params['aspect-width'] = $aspectWidth; 
    122       $params['aspect-height'] = $aspectHeight; 
    123     } 
    124  
    125     $minimumWidth = floor(aMediaTools::getAttribute('minimum-width')); 
    126     if ($minimumWidth) 
    127     { 
     112 
     113    if ($type === 'image') 
     114    { 
     115      // Now that we provide cropping tools, width and height should only exclude images 
     116      // that are too small to ever be cropped to that size 
     117      $minimumWidth = floor(aMediaTools::getAttribute('minimum-width')); 
     118      $width = floor(aMediaTools::getAttribute('width')); 
     119      $minimumWidth = max($minimumWidth, $width); 
     120      $minimumHeight = floor(aMediaTools::getAttribute('minimum-height')); 
     121      $height = floor(aMediaTools::getAttribute('height')); 
     122      $minimumHeight = max($minimumHeight, $height); 
     123      // Careful, aspect ratio can impose a bound on the other dimension 
     124      if ($minimumWidth && $aspectWidth) 
     125      { 
     126        $minimumHeight = max($minimumHeight, $minimumWidth * $aspectHeight / $aspectWidth); 
     127      } 
     128      if ($minimumHeight && $aspectHeight) 
     129      { 
     130        $minimumWidth = max($minimumWidth, $minimumHeight * $aspectWidth / $aspectHeight); 
     131      } 
     132      // We've updated these with implicit constraints from the aspect ratio, the width and height params, etc. 
     133      aMediaTools::setAttribute('minimum-width', $minimumWidth); 
     134      aMediaTools::setAttribute('minimum-height', $minimumHeight); 
    128135      $params['minimum-width'] = $minimumWidth; 
    129     } 
    130     $minimumHeight = floor(aMediaTools::getAttribute('minimum-height')); 
    131     if ($minimumHeight) 
    132     { 
    133136      $params['minimum-height'] = $minimumHeight; 
    134137    } 
    135     $width = floor(aMediaTools::getAttribute('width')); 
    136     if ($width) 
    137     { 
    138       $params['width'] = $width; 
    139     } 
    140     $height = floor(aMediaTools::getAttribute('height')); 
    141     if ($height) 
    142     { 
    143       $params['height'] = $height; 
    144     } 
    145  
     138    else 
     139    { 
     140      // TODO: performance of these is not awesome (it's a linear search).  
     141      // It would be more awesome with the right kind of indexing. For the  
     142      // aspect ratio test to be more efficient we'd have to store the lowest  
     143      // common denominator aspect ratio and index that. 
     144      if ($aspectWidth && $aspectHeight) 
     145      { 
     146        $params['aspect-width'] = $aspectWidth; 
     147        $params['aspect-height'] = $aspectHeight; 
     148      } 
     149 
     150      $minimumWidth = floor(aMediaTools::getAttribute('minimum-width')); 
     151      if ($minimumWidth) 
     152      { 
     153        $params['minimum-width'] = $minimumWidth; 
     154      } 
     155      $minimumHeight = floor(aMediaTools::getAttribute('minimum-height')); 
     156      if ($minimumHeight) 
     157      { 
     158        $params['minimum-height'] = $minimumHeight; 
     159      } 
     160      $width = floor(aMediaTools::getAttribute('width')); 
     161      if ($width) 
     162      { 
     163        $params['width'] = $width; 
     164      } 
     165      $height = floor(aMediaTools::getAttribute('height')); 
     166      if ($height) 
     167      { 
     168        $params['height'] = $height; 
     169      } 
     170    } 
     171     
    146172    // The media module is now an engine module. There is always a page, and that 
    147173    // page might have a restricted set of categories associated with it 
     
    162188    $this->pager->init(); 
    163189    $this->results = $this->pager->getResults(); 
     190 
    164191    aMediaTools::setSearchParameters( 
    165192      array("tag" => $tag, "type" => $type,  
     
    175202        $this->label = aMediaTools::getAttribute("label"); 
    176203      } 
    177       $this->limitSizes = false; 
    178       if ($aspectWidth || $aspectHeight || $minimumWidth || $minimumHeight || 
    179         $width || $height) 
    180       { 
    181         $this->limitSizes = true; 
    182       } 
     204      $this->limitSizes = ($minimumWidth || $minimumHeight); 
    183205    } 
    184206  } 
     
    215237      $parameters)); 
    216238  } 
    217  
     239   
     240  // Accept and store cropping information for a particular image which must already be part of the selection 
     241  public function executeCrop(sfRequest $request) 
     242  { 
     243    $selection = aMediaTools::getSelection(); 
     244    $id = $request->getParameter('id'); 
     245    $index = array_search($id, $selection); 
     246    if ($index === false) 
     247    { 
     248      error_log("ID is $id and not found in index which is " . implode(',', $selection)); 
     249      $this->forward404(); 
     250    } 
     251    $cropLeft = floor($request->getParameter('cropLeft')); 
     252    $cropTop = floor($request->getParameter('cropTop')); 
     253    $cropWidth = floor($request->getParameter('cropWidth')); 
     254    $cropHeight = floor($request->getParameter('cropHeight')); 
     255    $width = floor($request->getParameter('width')); 
     256    $height = floor($request->getParameter('height')); 
     257    $imageInfo = aMediaTools::getAttribute('imageInfo'); 
     258    $imageInfo[$id]['cropLeft'] = $cropLeft; 
     259    $imageInfo[$id]['cropTop'] = $cropTop; 
     260    $imageInfo[$id]['cropWidth'] = $cropWidth; 
     261    $imageInfo[$id]['cropHeight'] = $cropHeight; 
     262    $imageInfo[$id]['width'] = $width; 
     263    $imageInfo[$id]['height'] = $height; 
     264    aMediaTools::setAttribute('imageInfo', $imageInfo); 
     265  } 
     266   
    218267  public function executeMultipleAdd(sfRequest $request) 
    219268  { 
    220     $this->forward404Unless(aMediaTools::isMultiple()); 
    221269    $id = $request->getParameter('id') + 0; 
    222270    $item = Doctrine::getTable("aMediaItem")->find($id); 
    223271    $this->forward404Unless($item);  
    224272    $selection = aMediaTools::getSelection(); 
    225     $index = array_search($id, $selection); 
    226     // One occurrence each. If this changes we'll have to rethink 
    227     // the way reordering and deletion work (probably go by index). 
    228     if ($index === false) 
    229     { 
    230       $selection[] = $id; 
     273    if (!aMediaTools::isMultiple()) 
     274    { 
     275      $selection = array($id); 
     276    } 
     277    else 
     278    { 
     279      $index = array_search($id, $selection); 
     280      // One occurrence each. If this changes we'll have to rethink 
     281      // the way reordering and deletion work (probably go by index). 
     282      if ($index === false) 
     283      { 
     284        $selection[] = $id; 
     285      } 
    231286    } 
    232287    aMediaTools::setSelection($selection); 
     288    $imageInfo = aMediaTools::getAttribute('imageInfo'); 
     289    // Make no attempt to scrub out a previous crop, which could be handy 
     290    $imageInfo[$id]['width'] = $item->getWidth(); 
     291    $imageInfo[$id]['height'] = $item->getHeight(); 
     292    aMediaTools::setAttribute('imageInfo', $imageInfo); 
     293    // If no previous crop info is set, we must set an intial cropping mask 
     294    // so that the cropped media item id gets linked instead of the original 
     295    // media item. This is a little dangerous because JavaScript computes an 
     296    // intial crop mask on the client side. 
     297    aMediaTools::setDefaultCropDimensions($item); 
     298    if ((!aMediaTools::isMultiple()) && aMediaTools::getAttribute('type') !== 'image') 
     299    { 
     300      return $this->redirect('aMedia/selected'); 
     301    } 
    233302    return $this->renderComponent("aMedia", "multipleList"); 
    234303  } 
     
    236305  public function executeMultipleRemove(sfRequest $request) 
    237306  { 
    238     $this->forward404Unless(aMediaTools::isMultiple()); 
    239307    $id = $request->getParameter('id'); 
    240308    $item = Doctrine::getTable("aMediaItem")->find($id); 
     
    250318  } 
    251319 
     320  public function executeUpdateMultiplePreview(sfRequest $request) 
     321  { 
     322    return $this->renderComponent('aMedia', 'multiplePreview'); 
     323  } 
     324   
    252325  public function executeMultipleOrder(sfRequest $request) 
    253326  { 
    254327    $this->logMessage("*****MULTIPLE ORDER", "info"); 
    255328    $order = $request->getParameter('a-media-selection-list-item'); 
    256     $oldSelection = aMediaTools::getSelection(); 
     329    $oldSelection = aMediaTools::getSelection();     
    257330    $keys = array_flip($oldSelection); 
    258331    $selection = array(); 
     
    269342      $this->logMessage(">>>KEEPING " . $item->getId(), "info"); 
    270343    } 
    271     $this->logMessage(">>>SUCCEEDED: " . implode(", ", $selection), "info"); 
     344    $this->logMessage(">>>SUCCEEDED: " . implode(", ", $selection), "info");     
    272345    aMediaTools::setSelection($selection); 
    273346    return $this->renderComponent("aMedia", "multipleList"); 
    274347  } 
     348   
    275349  public function executeSelected(sfRequest $request) 
    276350  { 
    277     $controller = $this->getController(); 
    278351    $this->forward404Unless(aMediaTools::isSelecting()); 
    279     if (aMediaTools::isMultiple()) 
    280     { 
    281       $selection = aMediaTools::getSelection(); 
    282       // Ooops best to get this before clearing it huh 
    283       $after = aMediaTools::getAfter(); 
    284       // Oops I forgot to call this in the multiple case 
     352    $selection = aMediaTools::getSelection(); 
     353    $imageInfo = aMediaTools::getAttribute('imageInfo'); 
     354    // Get all the items in preparation for possible cropping 
     355    if (count($selection)) 
     356    { 
     357      $items = Doctrine::getTable('aMediaItem')->createQuery('m')->whereIn('m.id', $selection)->execute(); 
     358    } 
     359    else 
     360    { 
     361      $items = array(); 
     362    } 
     363    $items = aArray::listToHashById($items); 
     364    $newSelection = array(); 
     365    foreach ($selection as $id) 
     366    { 
     367      if (isset($imageInfo[$id]['cropLeft'])) 
     368      { 
     369        // We need to make a crop 
     370        $item = $items[$id]; 
     371        $crop = $item->findOrCreateCrop($imageInfo[$id]); 
     372        $crop->save(); 
     373        $newSelection[] = $crop->id; 
     374      } 
     375      else 
     376      { 
     377        $newSelection[] = $id; 
     378      } 
     379    } 
     380    // Ooops best to get this before clearing it huh 
     381    $after = aMediaTools::getAfter(); 
     382     
     383    // addParamsNoDelete never attempts to eliminate a field just because 
     384    // its value is empty. This is how we distinguish between cancellation 
     385    // and selecting zero items 
     386     
     387    if (!aMediaTools::isMultiple()) 
     388    { 
     389      // Call this too soon and you lose isMultiple 
    285390      aMediaTools::clearSelecting(); 
    286       // I thought about submitting this like a multiple select, 
    287       // but there's no clean way to implement that feature in 
    288       // addParam, and it wastes URL space anyway 
    289       // (remember the 1024-byte limit) 
    290        
    291       // addParamsNoDelete never attempts to eliminate a field just because 
    292       // its value is empty. This is how we distinguish between cancellation 
    293       // and selecting zero items 
    294       return $this->redirect( 
    295         aUrl::addParamsNoDelete($after, 
    296         array("aMediaIds" => implode(",", $selection)))); 
    297     } 
    298     // Single select 
    299     $id = $request->getParameter('id'); 
    300     $item = Doctrine::getTable("aMediaItem")->find($id); 
    301     $this->forward404Unless($item);  
    302     $after = aMediaTools::getAfter(); 
    303     $after = aUrl::addParams($after,  
    304       array("aMediaId" => $id)); 
    305     aMediaTools::clearSelecting(); 
    306     return $this->redirect($after); 
     391      if (count($newSelection)) 
     392      { 
     393        $after = aUrl::addParams($after,  
     394          array("aMediaId" => $newSelection[0])); 
     395        return $this->redirect($after); 
     396      } 
     397      else 
     398      { 
     399        $this->forward404(); 
     400      } 
     401    } 
     402    else 
     403    { 
     404      aMediaTools::clearSelecting(); 
     405      $url = aUrl::addParamsNoDelete($after, 
     406      array("aMediaIds" => implode(",", $newSelection))); 
     407      return $this->redirect($url); 
     408    } 
    307409  } 
    308410