Image-like content

Description

How to programmatically manipulate images on your Plone site.

Introduction

Plone supports image content in two forms:

  • As stand-alone content type, images will be visible in the sitemap. This is the case for the default Image content type, but you can create custom content types with similar properties.
  • As a field, the image is directly associated with one content object. Use Archetypes.fields.ImageField.

Custom image content type

If you want to have your custom content type behave like the stock Plone Image content type:

  • Inherit from the content class Products.ATContentType.content.image.ATImage and use the schema from that class.
  • When writing the GenericSetup XML of your type, follow the example of Image.xml.
  • Do not set workflow for your type in profiles/default/workflows.xml
<?xml version="1.0"?>
<object name="portal_workflow" meta_type="Plone Workflow Tool">

 <bindings>
  <type type_id="YourImageType"/>
 </bindings>
</object>

Image scales

When the image is uploaded, both field or content, Plone creates scaled-down versions from it by default.

These are configured using the ImageField ``sizes`` parameter. See the ImageField class notes here:

The default image scales for Image content are configured in:

Configuration:

sizes= {'large':   (768, 768),
        'preview': (400, 400),
        'mini':    (200, 200),
        'thumb':   (128, 128),
        'tile':    (64, 64),
        'icon':    (32, 32),
        'listing': (16, 16),
       },

More info:

getScale()

ImageField provides a getScale() method to get the scaled version of the image based on the sizes configuration key.

See example in __bobo_traverse__:

Accessing images

ImageField is mapped to a traversable attribute of your content type. E.g. if your content object has a field imageOne and is found at this URL:

http://yoursite/content

the image can be directly downloaded from:

http://yoursite/content/imageOne

Scaled versions for Image content (ATImage)

If you want different scales you can add image_XXX prefix where XXX is the corresponding scale name:

http://yoursite/content/imageOne/image_preview

In Plone 3 this hook is defined in __bobo_traverse__ in ATImage class: * https://github.com/plone/Products.ATContentTypes/blob/master/Products/ATContentTypes/content/image.py

portal_catalog and images

Do not index image objects themselves, as adding image data to the portal_catalog brain objects would greatly increase their site and make brain look-up slow.

Instead, index only image paths using getPhysicalPath(). When you need to display image using metadata columns, you can generate the image URL manually. Then, the image object will be woken up when the browser makes a HTTP request for the image.

Custom image scales and recreating scale data

Below is an example showing how to make custom image scales available in your Plone site.

  • Monkey-patch ATImages to have new scale versions available.
  • Have migration code which will run all through all ATImage content on the site and recreate their scale versions, thus populating image scale data for new scale versions also.
  • The new sizes are automatically effected to rich text editor image sizes options (active WYSIWYG editor on Plone site)

images.py:

""" Add alternative image sizes to default ATImage scales.
    NOTE: This does not effect available user interface options in the visual editor etc.
"""

import transaction
from zope.app.component.hooks import setHooks, setSite, getSite

from Products.Five.browser import BrowserView

from Products.ATContentTypes.content.image import ATImage
from Products.ATContentTypes.interface.image import IATImage

# Monkeypatch our new image sizes to be available in ATImage default scales.
# This will also affect the "image sizes" option in the WYSIWYG text editor.
ATImage.schema["image"].sizes.update({
    "custom1": (290, 290),
    "custom2": (210, 210),
    "custom_210_189": (210, 189),
    "custom_290_258": (290, 258),
})

class RescaleImages(BrowserView):
    """ Migration view to recreate all image scale versions on all Image content types on the site.

    To trigger this migration code, enter the view URL manually in the browser address bar::

        http://yourhost/site/@@rescale_images

    We assume that you are running Zope in the foreground, monitoring the console for messages.

    This code is designed to work with sites with plenty of images.
    Tested with > 5000 images.

    Note that you need to run this rescale code only once to migrate the existing image content.
    New images will have custom scale versions available when the images are created.
    """

    def __call__(self):
        """ View processing entry point.
        """

        portal = getSite()

        # Iterate through all Image content items on the site
        all_images = portal.portal_catalog(show_inactive=True, language="ALL", object_provides=IATImage.__identifier__)

        done = 0

        for brain in all_images:
            content = brain.getObject()

            # Access schema in Plone 4 / archetypes.schemaextender compatible way
            schema = content.Schema()

            # This will trigger ImageField scale rebuild
            if "image" in schema:
                schema["image"].createScales(content)
            else:
                print "Has bad ATImage schema:" + content.absolute_url()

            # Since this is a HUGE operation (think of resizing 2 GB images)
            # it is not a good idea to buffer the transaction in memory
            # (Zope default behavior).
            # Using subtransactions we hint Zope when it would be a good
            # time to buffer the changes on disk.
            # http://www.zodb.org/documentation/guide/transactions.html
            if done % 10 == 0:
                # Commit subtransaction for every 10th processed item
                transaction.commit(True)

            done += 1
            print "(%d / %d) created scales for image: %s" % (done, len(all_images), "/".join(content.getPhysicalPath()))

        # Final commit
        transaction.commit()

        # Note that when entire transaction is committed, there will be a
        # huuuge delay before the message below is returned to the browser.
        # This is because Zope is busy updating the ZODB storage.

        # Make simple HTTP 200 answer
        return "Recreated image scales for %d images" % len(all_images)

configure.zcml

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:five="http://namespaces.zope.org/five"
    xmlns:browser="http://namespaces.zope.org/browser"
    >
    <browser:page
        for="*"
        name="rescale_images"
        permission="cmf.ManagePortal"
        class=".images.RescaleImages"
        />
</configure>

Automatic image scales on ReferenceFields

Python code:

 from zope.component import adapts
 from zope.interface import implements, Interface
 from plone.app.imaging.interfaces import IImageScaleHandler


 def dereference(func_name):
     def new_func(self, instance, *args, **kw):
         if self.context is None:
             instance = self.reference_field.get(instance)
             self.context = instance.getPrimaryField()
         handler = IImageScaleHandler(self.context)
         func = getattr(handler, func_name)
         return func(instance, *args, **kw)
     return new_func


 class IReferenceField(Interface):
     """ marker """

 class ReferencedImageScaleHandler(object):
     """ proxy the standard image scale handler so that it operates on a referenced image """
     implements(IImageScaleHandler)
     adapts(IReferenceField)

     def __init__(self, context):
         self.reference_field = context
         self.context = None

     getScale = dereference('getScale')
     createScale = dereference('createScale')
     retrieveScale = dereference('retrieveScale')
storeScale = dereference('storeScale')

in configure.zcml:

<class class="Products.Archetypes.Field.ReferenceField">
  <implements interface=".IReferenceField"/>
</class>

<adapter
    factory=".ReferencedImageScaleHandler" />