0

Been struggling with this for days. I create an image algorithmically from some data. I also need to take an image and recreate the data, so I want to implement an accurate two-way operation, data -> image and image -> data, say d1 -> i1 -> d2.

Then I ran into some bugs and after a lot of struggling I realised that d1 != d2!

Most of the trouble appears to be in the lower right hand corner of the image, but, since creating the test code below, I note that the problem seems to be wider than this.

The code below illustrates this heart of this problem, which is the [img drawInRect:] message. It seems that if you have some image and you draw it in some rect, no matter what you do with scaling and insets and blend modes and so on, it does not accurately give you back the original image.

So my question is: how can I use [img drawInRect:] to render img accurately.

Here is some sample code to illustrate the problem.

#import "ViewController.h"

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UIImageView * topImage;
@property (weak, nonatomic) IBOutlet UIImageView * botImage;

@end

@implementation ViewController

- (IBAction)drawAction:(id)sender
{
    CGFloat w = self.topImage.bounds.size.width / 20;
    CGFloat h = self.topImage.bounds.size.height / 20;

    // Create random image
    UIGraphicsImageRendererFormat * format = UIGraphicsImageRendererFormat.defaultFormat;

    format.scale = 1;

    UIImage * top = [[[UIGraphicsImageRenderer alloc] initWithSize:CGSizeMake ( w, h )
                                format:format]
             imageWithActions: ^ ( UIGraphicsImageRendererContext * ctx )
    {
        for ( CGFloat x = 0; x < w; x ++ )
        {
            for ( CGFloat y = 0; y < w; y ++ )
            {
                if ( arc4random_uniform ( 2 ) )
                {
                    [ctx fillRect:CGRectMake( x, y, 1, 1 )];
                }
            }
        }
    }];

    // Duplicate image
    UIImage * bot = [[[UIGraphicsImageRenderer alloc] initWithSize:CGSizeMake ( w, h )
                                format:format]
             imageWithActions: ^ ( UIGraphicsImageRendererContext * ctx )
    {
        [top drawInRect:CGRectMake ( 0, 0, w, h )];
    }];

    // Done
    self.topImage.image = top;
    self.botImage.image = bot;
}

@end

This is inside a standard view controller with two image views and a button, as below.

why do these images differ

In the original problem I do not scale down by 20 as here - the 20 is just to illustrate, but it makes no difference if you work at a scale of 1.

Note the difference between the two images - note especially the bottom right hand corner! Why the difference?

FWIW after drawing the image I access the image data and build the data (e.g. d2) from there. Sometimes the image needs to be scaled and thus I want to use [img drawInRect:] first to render the image and afterwards to work with the image data. If the image is already the correct size then I use the image data directly and that sidesteps this problem, but still, I need to use [img drawInRect:] when the sizes differ and I would expect [img drawInRect:] to be faithful to the original!

TIA

skaak
  • 2,988
  • 1
  • 8
  • 16

1 Answers1

0

Almost a fix.

#import "ViewController.h"

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UIImageView * topImage;
@property (weak, nonatomic) IBOutlet UIImageView * botImage;

@end

@implementation ViewController

- ( void ) viewDidLoad
{
    super.viewDidLoad;

    self.topImage.layer.magnificationFilter = kCAFilterNearest;
    self.botImage.layer.magnificationFilter = kCAFilterNearest;
}

- ( IBAction ) drawAction:( id ) sender
{
    CGFloat wt = self.topImage.bounds.size.width / 20;
    CGFloat ht = self.topImage.bounds.size.height / 20;

    // Create random image
    UIGraphicsImageRendererFormat * format = UIGraphicsImageRendererFormat.defaultFormat;

    format.scale = 1;

    UIImage * top = [[[UIGraphicsImageRenderer alloc] initWithSize:CGSizeMake ( wt, ht )
                                format:format]
             imageWithActions: ^ ( UIGraphicsImageRendererContext * ctx )
    {
        CGContextSetShouldAntialias ( ctx.CGContext, NO );
        CGContextSetBlendMode ( ctx.CGContext, kCGBlendModeCopy );

        for ( CGFloat x = 0; x < wt; x ++ )
        {
            for ( CGFloat y = 0; y < wt; y ++ )
            {
                if ( arc4random_uniform ( 2 ) )
                {
                    [ctx fillRect:CGRectMake( x, y, 1, 1 )];
                }
            }
        }
    }];

    // Duplicate image
    CGFloat wb = self.topImage.bounds.size.width / 10;
    CGFloat hb = self.topImage.bounds.size.height / 10;

    UIImage * bot = [[[UIGraphicsImageRenderer alloc] initWithSize:CGSizeMake ( wb, hb )
                                format:format]
             imageWithActions: ^ ( UIGraphicsImageRendererContext * ctx )
    {
        CGContextSetInterpolationQuality ( ctx.CGContext, kCGInterpolationNone );
        CGContextSetShouldAntialias ( ctx.CGContext, NO );

        [top drawInRect:CGRectMake ( 0, 0, wb, hb )];
        // Use this if images are the same ...
        // [top drawAsPatternInRect:CGRectMake ( 0, 0, w, h )];
    }];

    // Done
    self.topImage.image = top;
    self.botImage.image = bot;
}

@end

In this solution, note that the scale of the two images are different, just to illustrate.

The [img drawInRect:] just does not want to come to the party and there remains this problem, especially in the lower right hand corner, for scaled images as in the example below.

almost a fix

It seems the scaling messes it up, so if the sizes for both images are the same, rather use e.g. [img drawAsPatternInRect:] as also indicated in the comments. Otherwise when you scale, prepare for some loss. Also, if you put your nose to the screen and try to look through it you may see an image of Jobs.

skaak
  • 2,988
  • 1
  • 8
  • 16