Невозможно правильно прочитать данные о цвете пользовательского изображения, созданного из UIView

#ios #objective-c #uiview #bitmap #uiimage

#iOS #objective-c #uiview #растровое изображение #uiimage

Вопрос:

Что должно произойти:

Пользователь рисует (используя, например, Apple Pencil) в UIView. Когда он закончит, UIImage создается из этого представления, данные ARGB считываются один за другим и, наконец, сохраняются в NSData.

Позже NSData снова привыкает к воссозданию изображения. (Я знаю, что могу добиться чего-то подобного намного проще, но, пожалуйста, просто предположите, что для подхода есть веская причина.)

Что на самом деле происходит:

Пользователь переходит в представление, UIImage берется, но когда код обращается к данным изображения, происходит что-то связанное: Данные, похоже, не соответствуют изображению, что становится очевидным, когда они используются для восстановления изображения.

И вот код.

Из UIView

Инициализация

 - (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        self.layer.allowsEdgeAntialiasing=YES;
        self.backgroundColor=[UIColor clearColor];
    }
    return self;
}
  

Рисование

 - (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [[event allTouches]anyObject];
    CGPoint touchLocation = [touch locationInView:self];
    if (myImage!=nil) {
        UIImageView * iV = [[UIImageView alloc] initWithFrame:self.bounds];
        iV.image = myImage;
        [self insertSubview:iV atIndex:0];

    }
    myPath = [UIBezierPath bezierPath];
    [myPath moveToPoint:touchLocation];
}

- (void) touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [[event allTouches]anyObject];
    CGPoint touchLocation = [touch locationInView:self];
    [myPath addLineToPoint:touchLocation];
    [self setNeedsDisplay];
}

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        myImage = self.imageRepresentation;
}


- (void) drawRect:(CGRect)rect {

    self.context = UIGraphicsGetCurrentContext();
    [[UIColor blackColor]setStroke];
    myPath.lineWidth=3.0;
    [myPath stroke];

}
  

Получение UIImage из UIView

 -(UIImage *)imageRepresentation{

    UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 1.0);
    [self.layer renderInContext:UIGraphicsGetCurrentContext()];
    myImage= UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return myImage;

}
  

Чтение и преобразование данных изображения

 -(NSData *) pixel{

    int sidelength = (int) self.myImage.size.width;
    int numberData = sidelength * sidelength * 4;

    CFDataRef pixelData = CGDataProviderCopyData(CGImageGetDataProvider(self.myImage.CGImage));
    const UInt8 * data = CFDataGetBytePtr(pixelData);

    UInt8 pixel[numberData];
    for (int y=0; y<sidelength; y  ) {
        for (int x=0; x<sidelength; x  ) {
            int components = sidelength * y   x * 4;
            UInt8 a = data[components];
            UInt8 r = data[components 1];
            UInt8 g = data[components 2];
            UInt8 b = data[components 3];

            pixel[components]=a;
            pixel[components 1]=r;
            pixel[components 2]=g;
            pixel[components 3]=b;

        }
    }
    CFRelease(pixelData);
    return [NSData dataWithBytes:pixel length:sidelength*sidelength*4];;
  

Из другого класса

Извлечение изображения с помощью NSData

 -(UIImage *) getImage{

    UInt8 * pixel=malloc(sizeof(UInt8)*self.numberBytes);
    pixel = (UInt8 *) booleanData.bytes;

    const int Width = self.size.width;
    const int Height = self.size.height;
    const int ComponentsPerPixel = 4;

    const size_t BitsPerComponent = 8;
    const size_t BytesPerRow=((BitsPerComponent * Width) / 8) * ComponentsPerPixel;
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef gtx = CGBitmapContextCreate(amp;pixel[0], Width, Height, BitsPerComponent, BytesPerRow, colorSpace, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host);

    CGImageRef toCGImage = CGBitmapContextCreateImage(gtx);
    UIImage * image = [[UIImage alloc] initWithCGImage:toCGImage];
    return image;


}
  

Вот пример результатов:

Исходный чертеж, визуализированный с использованием «imageRepresentation»:

введите описание изображения здесь

Полученное изображение с использованием «pixel» и «getImage»:

введите описание изображения здесь

Я ожидаю, что виновником будет метод «пикселей», потому что я проверил его результаты и обнаружил, что они уже довольно неправильные. Кроме того, я протестировал «getImage» в другой настройке, и он работал просто отлично. Но странно то, что это также верно для «пикселей». Так возможно ли, что UIView отображается способом, отличным от, скажем, png или jpg? Я проверил документацию для UIGraphicsBeginImageContextWithOptions, но это не дало мне полезной подсказки.

Комментарии:

1. Вы предполагаете, что ваше изображение представляет собой квадрат — его высота равна его ширине. Вы уверены, что это предположение верно? Еще одна вещь:

2. Спасибо. Да, я инициализирую представление (как LetterView, подкласс UIView) из родительского представления следующим образом: self.letterView = [[LetterView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, frame.size.width)];

Ответ №1:

Есть две проблемы. Во-первых, вы не вставляете все данные изображения в pixel массив в pixel методе.

Если ваше изображение имеет размер 100×100, то общее количество байтов равно 10000, умноженное на 4. В вашем for цикле самый большой пиксель, который вы получите, будет 100×100 100×4, что равно 10400. Остальные пиксели содержат мусор.

Этот цикл выполнит эту работу:

 for (unsigned long i = 0; i < numberData; i  = 4) {
    pixel[i] = data[i];
    pixel[i   1] = data[i   1];
    pixel[i   2] = data[i   2];
    pixel[i   3] = data[i   3];
}
  

Вторая проблема более интересна. Воссозданное изображение будет искажено, если его ширина не равна 64. В нем есть какая-то ошибка CGBitmapContextCreate — я отправил эту ошибку в Apple около шести лет назад, и они заявили, что исправили ее, но ее можно легко воспроизвести при определенных условиях.

Я запустил ваш код (с новым циклом), и он отлично работает с UIView размером 128×128, но возвращает искаженное изображение при размере 100×100.

Комментарии:

1. Вау, эта первая часть граничит с конфузом. Думаю, мне придется брать уроки подсчета. Большое спасибо! Кстати, я добавил некоторый код в meatime, который преобразует изображение в формат png, и, похоже, не сталкиваюсь с этой второй проблемой. Но в любом случае, если я это сделаю, теперь я знаю, что делать.