how to cache images

  • I have an app which displays gif images in an ImageView.

    NSString *path = @"/some/path/to/a.gif";
    NSURL *url = [ [ NSURL alloc ] initWithScheme: @"http"  host: @"www.some.host"  path: path ];
    NSImage *image = [ [ NSImage alloc ] initByReferencingURL: url ];
    [ self.imageView setImage: image ];

    works fine, but is sometimes slow. Also the images tend to be mostly the same, so I thought to save the loaded  images into my Application Support folder.

    First I tried: [ NSArchiver archiveRootObject: image toFile: @"/tmp/anImage" ] but this archives the Url, NOT the data.

    Then I tried: [ image setDelegate: self ] but the NSImageDelegate method:
    - (void)image:(NSImage *)image didLoadRepresentation:(NSImageRep *)rep withStatus:(NSImageLoadStatus)status
    does NOT get called.

    So: what would be a sensible way to store my loaded pictures?

    Gerriet.
  • On Jun 8, 2013, at 4:25 AM, Gerriet M. Denkmann wrote:

    > NSImage *image = [ [ NSImage alloc ] initByReferencingURL: url ];

    > First I tried: [ NSArchiver archiveRootObject: image toFile: @"/tmp/anImage" ] but this archives the Url, NOT the data.

    That's a documented consequence of using -initByReferencingURL:.  If you use -initWithContentsOfURL: that shouldn't happen.

    Alternatively, you could force the image to load and then use +[NSBitmapImageRep representationOfImageRepsInArray:usingType:properties:] to create an NSData from the image's representations and save that to file.

    Another approach would be to use NSURLConnection to obtain the image data.  That should use the URL cache by default.  If it doesn't, you can configure it to.  That way, you wouldn't have to manage the cache yourself.  (You should make sure the host isn't claiming that the image has to be reloaded every time – i.e. can't be cached.  Maybe NSImage would use the URL cache already except the host is preventing it.)

    Regards,
    Ken
  • On Jun 8, 2013, at 8:01 AM, Ken Thomases <ken...> wrote:

    > Alternatively, you could force the image to load and then use +[NSBitmapImageRep representationOfImageRepsInArray:usingType:properties:] to create an NSData from the image's representations and save that to file.

    Not a good idea. If you do this with images that were originally JPEG, you’ll degrade the image quality if you save it out as JPEG again, since it’ll have to run the pixels through the lossy encoder again. Or if you save it in a different format the file size will go up a lot.

    > Another approach would be to use NSURLConnection to obtain the image data.

    Or -[NSData dataWithContentsOfURL:options:error:] if you want the short-and-easy form. I wouldn’t recommend this in a real app, though, because it blocks while loading the file.

    —Jens
  • On Jun 8, 2013, at 2:05 PM, Jens Alfke wrote:

    > On Jun 8, 2013, at 8:01 AM, Ken Thomases <ken...> wrote:
    >
    >> Alternatively, you could force the image to load and then use +[NSBitmapImageRep representationOfImageRepsInArray:usingType:properties:] to create an NSData from the image's representations and save that to file.
    >
    > Not a good idea. If you do this with images that were originally JPEG, you’ll degrade the image quality if you save it out as JPEG again, since it’ll have to run the pixels through the lossy encoder again. Or if you save it in a different format the file size will go up a lot.

    I haven't done the experiment, but I don't believe this is necessarily true.  NSBitmapImageRep is documented (in the Snow Leopard release notes) as keeping the original image data and not re-encoding or exploding file sizes on being saved.

    "You should see less encoding and decoding of bitmap data as CGImages. If you initialize a NSImage from a JPEG file, then draw it in a PDF, you should get a PDF of the same file size as the original JPEG. In Leopard you'd see a PDF the size of the decompressed image."

    Search for "NSBitmapImageRep: CoreGraphics impedance matching and performance notes" on this page:
    https://developer.apple.com/library/mac/releasenotes/Cocoa/AppKitOlderNotes
    .html#X10_6Notes


    Regards,
    Ken
  • On Jun 8, 2013, at 12:24 PM, Ken Thomases <ken...> wrote:

    > I haven't done the experiment, but I don't believe this is necessarily true.  NSBitmapImageRep is documented (in the Snow Leopard release notes) as keeping the original image data and not re-encoding or exploding file sizes on being saved.

    I did not know this — guess I haven’t been reading the release notes closely enough.

    —Jens
  • On 9 Jun 2013, at 06:23, Jens Alfke <jens...> wrote:

    >
    > On Jun 8, 2013, at 12:24 PM, Ken Thomases <ken...> wrote:
    >
    >> I haven't done the experiment, but I don't believe this is necessarily true.  NSBitmapImageRep is documented (in the Snow Leopard release notes) as keeping the original image data and not re-encoding or exploding file sizes on being saved.
    >
    > I did not know this — guess I haven’t been reading the release notes closely enough.

    url = some/picture.gif
    NSDataReadingOptions mask = 0; // NSDataReadingUncached
    NSData *data = [ NSData dataWithContentsOfURL: url options: mask error: &outError ];
    got 19420 bytes

    NSImage *image = [ [ NSImage alloc ] initWithContentsOfURL: url ];
    BOOL ok = [ NSArchiver archiveRootObject: image toFile: @"/tmp/anImage" ];
    got 307559 bytes (NSKeyedArchiver adds another half  kB)

    This 16-fold increase of data is - regardless of image quality - not acceptable for my purposes.

    So I will use dataWithContentsOfURL (and switch to NSURLConnection once the unresponsiveness of the app becomes annoying).

    I still have no idea why my image delegate never gets messaged. But not important anymore.

    Kind regards,

    Gerriet.
  • On Jun 8, 2013, at 5:39 PM, "Gerriet M. Denkmann" <gerriet...> wrote:

    >
    > On 9 Jun 2013, at 06:23, Jens Alfke <jens...> wrote:
    >
    >>
    >> On Jun 8, 2013, at 12:24 PM, Ken Thomases <ken...> wrote:
    >>
    >>> I haven't done the experiment, but I don't believe this is necessarily true.  NSBitmapImageRep is documented (in the Snow Leopard release notes) as keeping the original image data and not re-encoding or exploding file sizes on being saved.
    >>
    >> I did not know this — guess I haven’t been reading the release notes closely enough.
    >
    > url = some/picture.gif
    > NSDataReadingOptions mask = 0;    //    NSDataReadingUncached
    > NSData *data = [ NSData dataWithContentsOfURL: url options: mask error: &outError ];
    > got 19420 bytes
    >
    > NSImage *image = [ [ NSImage alloc ] initWithContentsOfURL: url ];
    > BOOL ok = [ NSArchiver archiveRootObject: image toFile: @"/tmp/anImage" ];
    > got 307559 bytes (NSKeyedArchiver adds another half  kB)
    >
    > This 16-fold increase of data is - regardless of image quality - not acceptable for my purposes.

    This is why you don't use NSArchiver for data blobs: it writes them out as Base64-encoded plist strings.

    --Kyle Sluder
  • On Jun 8, 2013, at 7:39 PM, Gerriet M. Denkmann wrote:

    > url = some/picture.gif
    > NSDataReadingOptions mask = 0;    //    NSDataReadingUncached
    > NSData *data = [ NSData dataWithContentsOfURL: url options: mask error: &outError ];
    > got 19420 bytes
    >
    > NSImage *image = [ [ NSImage alloc ] initWithContentsOfURL: url ];
    > BOOL ok = [ NSArchiver archiveRootObject: image toFile: @"/tmp/anImage" ];
    > got 307559 bytes (NSKeyedArchiver adds another half  kB)
    >
    > This 16-fold increase of data is - regardless of image quality - not acceptable for my purposes.

    This isn't the technique I recommended.  Don't archive the image object.  Create a data representation using +[NSBitmapImageRep representationOfImageRepsInArray:usingType:properties:].

    $ python
    ...
    >>> from AppKit import *
    >>> u = NSURL.URLWithString_("https://upload.wikimedia.org/wikipedia/commons/e/e2/Sunflower_as_gif_small.
    gif
    ")
    >>> d1 = NSData.dataWithContentsOfURL_(u)
    >>> d1.length()
    57270
    >>> i = NSImage.alloc().initWithContentsOfURL_(u)
    >>> d2 = NSBitmapImageRep.representationOfImageRepsInArray_usingType_properties_(i.representations(), NSGIFFileType, None)
    >>> d2.length()
    54377

    It actually got smaller!

    Regards,
    Ken
  • On 9 Jun 2013, at 08:57, Kyle Sluder <kyle...> wrote:

    > On Jun 8, 2013, at 5:39 PM, "Gerriet M. Denkmann" <gerriet...> wrote:
    >
    >>
    >> On 9 Jun 2013, at 06:23, Jens Alfke <jens...> wrote:
    >>
    >>>
    >>> On Jun 8, 2013, at 12:24 PM, Ken Thomases <ken...> wrote:
    >>>
    >>>> I haven't done the experiment, but I don't believe this is necessarily true.  NSBitmapImageRep is documented (in the Snow Leopard release notes) as keeping the original image data and not re-encoding or exploding file sizes on being saved.
    >>>
    >>> I did not know this — guess I haven’t been reading the release notes closely enough.
    >>
    >> url = some/picture.gif
    >> NSDataReadingOptions mask = 0;    //    NSDataReadingUncached
    >> NSData *data = [ NSData dataWithContentsOfURL: url options: mask error: &outError ];
    >> got 19420 bytes
    >>
    >> NSImage *image = [ [ NSImage alloc ] initWithContentsOfURL: url ];
    >> BOOL ok = [ NSArchiver archiveRootObject: image toFile: @"/tmp/anImage" ];
    >> got 307559 bytes (NSKeyedArchiver adds another half  kB)
    >>
    >> This 16-fold increase of data is - regardless of image quality - not acceptable for my purposes.
    >
    > This is why you don't use NSArchiver for data blobs: it writes them out as Base64-encoded plist strings.

    My gif-image has only one representation:
    "NSBitmapImageRep 0x109cdf610 Size={320, 320} ColorSpace=Device RGB colorspace BPS=8 BPP=32 Pixels=320x320 Alpha=NO Planar=NO Format=0 CurrentBacking=<CGImageRef: 0x10c878eb0> CGImageSource=0x116e02580"

    And the archive contains (among a few other things, like an NSColor) an NSBitmapImageRep, which contains a characterArray of 307394 chars = 3 * 320 * 320 + 194.

    This is the archiver format, not the property list format.

    So it seems that in initialising my image with gif-data, the original data was somehow lost.

    And when I do:
    NSArray *imageReps = [ self.imageView.image representations];
    NSData *gifData = [ NSBitmapImageRep representationOfImageRepsInArray: imageReps usingType: NSGIFFileType properties: nil ];

    it gets smaller indeed. From 19k to about 3k -- But: while the original was an animated gif,
    the new gifData is no longer animated. Not very useful to me.

    I looked at the properties parameter, but did not see anything relating to animation.

    Kind regards,

    Gerriet.
  • On Sat, Jun 8, 2013, at 10:23 PM, Gerriet M. Denkmann wrote:
    > This is the archiver format, not the property list format.

    My mistake. _Keyed_ archives are property lists. And thankfully the
    binary plist format is smart enough not to inflate the image data.

    --Kyle Sluder
previous month june 2013 next month
MTWTFSS
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Go to today