CGContextDrawImage малює зображення догори дном при передачі UIImage.CGImage


179

Хтось знає, чому CGContextDrawImageб малювати моє зображення догори ногами? Я завантажую зображення із своєї програми:

UIImage *image = [UIImage imageNamed:@"testImage.png"];

А потім просто попросити основну графіку намалювати її в моєму контексті:

CGContextDrawImage(context, CGRectMake(0, 0, 145, 15), image.CGImage);

Він відображається в потрібному місці та розмірах, але зображення перевернуте. Я, мабуть, пропускаю щось справді очевидне?

Відповіді:


238

Замість

CGContextDrawImage(context, CGRectMake(0, 0, 145, 15), image.CGImage);

Використовуйте

[image drawInRect:CGRectMake(0, 0, 145, 15)];

В середині ваших CGcontextметодів початку / кінця .

Це дозволить малювати зображення з правильною орієнтацією у вашому поточному контексті зображення - я майже впевнений, що це пов'язане з UIImageтриманням на знаннях орієнтації, тоді як CGContextDrawImageметод отримує основні дані необробленого зображення без розуміння орієнтації.


19
Це рішення не дозволяє використовувати функції CG на своєму зображенні, такі як CGContextSetAlpha (), тоді як 2-а відповідь - це.
samvermette

2
Хороша думка, хоча в основному вам не потрібні спеціальні альфа-рівні для нанесення зображення (зазвичай вони заздалегідь випікаються на зображення для речей, для яких потрібна альфа). В основному мій девіз: використовуйте трохи коду, як можете, оскільки більше коду означає більше шансів на помилки.
Кендалл Гельмстеттер Гельнер

Привіт, що ви маєте на увазі під час початку / кінця методів CGContext, чи можете ви дати мені більше зразкового коду. Я намагаюся десь зберігати контекст і використовувати контекст, щоб намалювати зображення в
водханг

2
Хоча це може працювати для Rusty, - [UIImage drawInRect:] є проблема, що він не є безпечним для потоків. Для мого конкретного застосування, тому я в першу чергу використовую CGContextDrawImage ().
Олі

2
Просто для уточнення: Core Graphics побудований на OS X, де система координат перевернута вниз в порівнянні з iOS (y починаючи з нижньої лівої сторони в OS X). Ось чому Core Graphics малює зображення догори дном, тоді як drawInRect обробляє це для вас
borchero

213

Навіть після застосування всього, що я згадав, у мене все ж були драми із зображеннями. Зрештою, я щойно використав Gimp, щоб створити "перевернуту вертикальну" версію всіх моїх зображень. Тепер мені не потрібно використовувати будь-які перетворення. Сподіваємось, це не спричинить подальших проблем на шляху.

Хтось знає, чому CGContextDrawImage малював моє зображення догори дном? Я завантажую зображення із своєї програми:

Quartz2d використовує іншу систему координат, де походження знаходиться в лівому нижньому куті. Отже, коли Quartz малює піксель x [5], y [10] зображення розміром 100 * 100, цей піксель малюється в лівому нижньому куті замість лівого верхнього кута. Таким чином викликаючи «перевернутий» образ.

Система координат x збігається, тому вам потрібно перегорнути y координати.

CGContextTranslateCTM(context, 0, image.size.height);

Це означає, що ми перевели зображення на 0 одиниць на осі x та на висоту зображень на осі y. Однак це лише означає, що наш образ все ще перевернутий, просто намальований "image.size.height" внизу там, де ми хочемо його намалювати.

Посібник із програмування Quartz2D рекомендує використовувати ScaleCTM та передавати негативні значення для перегортання зображення. Ви можете використовувати наступний код для цього -

CGContextScaleCTM(context, 1.0, -1.0);

Поєднайте ці дві перед CGContextDrawImageдзвінком, і ви повинні мати правильне малювання.

UIImage *image = [UIImage imageNamed:@"testImage.png"];    
CGRect imageRect = CGRectMake(0, 0, image.size.width, image.size.height);       

CGContextTranslateCTM(context, 0, image.size.height);
CGContextScaleCTM(context, 1.0, -1.0);

CGContextDrawImage(context, imageRect, image.CGImage);

Будьте обережні, якщо координати imageRect не збігаються з зображеннями, оскільки ви можете отримати непередбачувані результати.

Щоб перетворити назад координати:

CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0, -imageRect.size.height);

це добре, але я виявив, що на інші речі впливає переклад і масштаб, навіть коли я намагався повернутись до 0,0 та 1,0,1,0, я пішов із рішенням Кендалла, врешті-решт, але я розумію, що це використовує UIImage, а не CGImage нижчого рівня, з якими ми працюємо тут
PeanutPower,

3
Якщо ви використовуєте drawLayer і хочете намалювати CGImageRef у контексті, ця методика тут просто хоче, щоб лікар замовив! Якщо у вас є пряма для зображення, тоді CGContextTranslateCTM (контекст, 0, image.size.height + image.origin.y), а потім встановіть rect.origin.y на 0 перед CGContextDrawImage (). Дякую!
Девід Н

Одного разу у мене виникли проблеми з ImageMagick, де камера iPhone спричинила неправильну орієнтацію. Виявляється, це робимо орієнтаційний прапор у файлі зображень, тому портретні зображення були, скажімо, 960px x 640px із прапорцем, встановленим на "ліворуч", як у лівій частині знаходиться вгорі (або щось близьке до цього). Ця тема може допомогти: stackoverflow.com/questions/1260249/…
Харі Карам Сінгх

8
Чому ви не використовуєте CGContextSaveGState()та CGContextRestoreGState()не зберігаєте та не відновлюєте матрицю перетворення?
Ja͢ck

20

Найкраще для обох світів використовуйте UIImage drawAtPoint:або drawInRect:поки ще вказуєте свій власний контекст:

UIGraphicsPushContext(context);
[image drawAtPoint:CGPointZero]; // UIImage will handle all especial cases!
UIGraphicsPopContext();

Крім того, ви уникаєте модифікувати ваш контекст, CGContextTranslateCTMабо з CGContextScaleCTMяким відповідає друга відповідь.


Це відмінна ідея, але для мене це призвело до зображення, яке було і догори дном, і зліва направо. Я здогадуюсь, що результати будуть змінюватись від зображення до зображення.
Люк Роджерс

Може, це перестало працювати з пізнішими версіями iOS? У той час він працював на все для мене.
Rivera

Я думаю, це було насправді до того, як я створював контекст. Як тільки я почав використовувати UIGraphicsBeginImageContextWithOptionsзамість того, щоб просто запросити нове CGContext, здавалося, він працює.
Люк Роджерс

12

Відповідні документи Quartz2D: https://developer.apple.com/library/ios/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GraphicsDrawingOverview/GraphicsDrawingOverview.html#//apple_ref/doc/uid/TP40010156-CH14

Перегортання системи координат за замовчуванням

Перегортання малюнка UIKit модифікує підтримуючий CALayer для вирівнювання середовища малювання, що має систему координат LLO, та систему координат за замовчуванням UIKit. Якщо ви використовуєте лише методи UIKit і функції для малювання, вам не потрібно перегортати CTM. Однак, якщо ви поєднуєте дзвінки з функцією Core Graphics або Image I / O функції з дзвінками UIKit, гортання CTM може знадобитися.

Зокрема, якщо ви малюєте зображення або документ PDF, зателефонувавши безпосередньо до функцій Core Graphics, об'єкт відображається перевернутим у контексті подання. Щоб правильно відобразити зображення та сторінки, потрібно перевернути CTM.

Щоб перевернути об’єкт, намальований у контексті основної графіки, щоб він відображався правильно, коли він відображався у перегляді UIKit, потрібно змінити CTM у два етапи. Ви перекладаєте початок у лівий верхній кут області малювання, а потім застосовуєте масштабний переклад, змінюючи y-координату на -1. Код для цього виглядає приблизно так:

CGContextSaveGState(graphicsContext);
CGContextTranslateCTM(graphicsContext, 0.0, imageHeight);
CGContextScaleCTM(graphicsContext, 1.0, -1.0);
CGContextDrawImage(graphicsContext, image, CGRectMake(0, 0, imageWidth, imageHeight));
CGContextRestoreGState(graphicsContext);

10

Якщо когось зацікавило рішення, яке не потребує мозгу, для нанесення зображення у користувальницьку прямокутницю в контексті:

 func drawImage(image: UIImage, inRect rect: CGRect, context: CGContext!) {

    //flip coords
    let ty: CGFloat = (rect.origin.y + rect.size.height)
    CGContextTranslateCTM(context, 0, ty)
    CGContextScaleCTM(context, 1.0, -1.0)

    //draw image
    let rect__y_zero = CGRect(origin: CGPoint(x: rect.origin.x, y:0), size: rect.size)
    CGContextDrawImage(context, rect__y_zero, image.CGImage)

    //flip back
    CGContextScaleCTM(context, 1.0, -1.0)
    CGContextTranslateCTM(context, 0, -ty)

}

Зображення буде масштабуватись, щоб заповнити пряму.


7

Я не впевнений UIImage, але така поведінка зазвичай виникає, коли координати перекидаються. Більшість систем координат OS X мають своє походження в нижньому лівому куті, як у Postscript та PDF. Але CGImageсистема координат бере свій початок у верхньому лівому куті.

Можливі рішення можуть включати isFlippedвластивість або scaleYBy:-1афінну трансформацію.


3

UIImageмістить CGImageосновний вміст, а також коефіцієнти масштабування та орієнтації. Оскільки CGImageі різні його функції походять від OSX, він очікує, що система координат буде перевернута вниз в порівнянні з iPhone. Коли ви створюєтеUIImage , він за замовчуванням налаштовує перевернуту вгору орієнтацію (ви можете змінити це!). Використовувати . CGImageвластивість доступу до дуже потужних CGImageфункцій, але малюнок на екрані iPhone і т.д. найкраще робити UIImageметодами.


1
як змінити орієнтацію UIImage за замовчуванням за замовчуванням?
MegaManX

3

Довідкова відповідь з кодом Свіфта

Кварцова 2D графіка використовує систему координат із початком у нижньому лівому куті, тоді як UIKit в iOS використовує систему координат із початком у верхньому лівому куті. Зазвичай все працює добре, але, виконуючи деякі графічні операції, вам доведеться самостійно змінювати систему координат. У документації зазначено:

Деякі технології встановлюють свої графічні контексти, використовуючи іншу систему координат за замовчуванням, ніж та, яку використовує Quartz. Щодо кварцу така система координат є модифікованою системою координат і повинна компенсуватися при виконанні деяких операцій малювання кварцу. Найпоширеніша модифікована система координат розміщує початок у верхньому лівому куті контексту та змінює вісь y у напрямку до нижньої частини сторінки.

Це явище можна побачити у двох наступних примірниках користувацьких поглядів, які малюють зображення у своїх drawRectметодах.

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

На лівій стороні зображення перевернуте догори дном, а на правій стороні система координат була переведена та масштабована так, що початок знаходиться у верхньому лівому куті.

Зображення вниз

override func drawRect(rect: CGRect) {

    // image
    let image = UIImage(named: "rocket")!
    let imageRect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)

    // context
    let context = UIGraphicsGetCurrentContext()

    // draw image in context
    CGContextDrawImage(context, imageRect, image.CGImage)

}

Модифікована система координат

override func drawRect(rect: CGRect) {

    // image
    let image = UIImage(named: "rocket")!
    let imageRect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)

    // context
    let context = UIGraphicsGetCurrentContext()

    // save the context so that it can be undone later
    CGContextSaveGState(context)

    // put the origin of the coordinate system at the top left
    CGContextTranslateCTM(context, 0, image.size.height)
    CGContextScaleCTM(context, 1.0, -1.0)

    // draw the image in the context
    CGContextDrawImage(context, imageRect, image.CGImage)

    // undo changes to the context
    CGContextRestoreGState(context)
}


2

Ми можемо вирішити цю проблему за допомогою тієї ж функції:

UIGraphicsBeginImageContext(image.size);

UIGraphicsPushContext(context);

[image drawInRect:CGRectMake(gestureEndPoint.x,gestureEndPoint.y,350,92)];

UIGraphicsPopContext();

UIGraphicsEndImageContext();

1

drawInRectце, безумовно, шлях. Ось ще одна дрібниця, яка стане корисною для цього. Зазвичай зображення і прямокутник, в який він збирається входити, не відповідають. У такому випадку drawInRectмалюнок розтягнеться. Ось швидкий і прохолодний спосіб переконатись, що співвідношення сторін зображення не було змінено шляхом перетворення трансформації (яка повинна відповідати всій справі):

//Picture and irect don't conform, so there'll be stretching, compensate
    float xf = Picture.size.width/irect.size.width;
    float yf = Picture.size.height/irect.size.height;
    float m = MIN(xf, yf);
    xf /= m;
    yf /= m;
    CGContextScaleCTM(ctx, xf, yf);

    [Picture drawInRect: irect];

1

Swift 3 CoreGraphics рішення

Якщо ви хочете використовувати CG з будь-якої причини, замість UIImage, ця конструкція Swift 3 на основі попередніх відповідей вирішила проблему для мене:

if let cgImage = uiImage.cgImage {
    cgContext.saveGState()
    cgContext.translateBy(x: 0.0, y: cgRect.size.height)
    cgContext.scaleBy(x: 1.0, y: -1.0)
    cgContext.draw(cgImage, in: cgRect)
    cgContext.restoreGState()
}

1

Це трапляється тому, що у QuartzCore є система координат «знизу-вліво», а у UIKit - «зверху-вліво».

У випадку, якщо ви можете продовжити CGContext :

extension CGContext {
  func changeToTopLeftCoordinateSystem() {
    translateBy(x: 0, y: boundingBoxOfClipPath.size.height)
    scaleBy(x: 1, y: -1)
  }
}

// somewhere in render 
ctx.saveGState()
ctx.changeToTopLeftCoordinateSystem()
ctx.draw(cgImage!, in: frame)

1

Я використовую це розширення Core Graphics Swift 5 , чисте розширення Core Graphics, яке правильно обробляє ненульові джерела в прямому образі зображення:

extension CGContext {

    /// Draw `image` flipped vertically, positioned and scaled inside `rect`.
    public func drawFlipped(_ image: CGImage, in rect: CGRect) {
        self.saveGState()
        self.translateBy(x: 0, y: rect.origin.y + rect.height)
        self.scaleBy(x: 1.0, y: -1.0)
        self.draw(image, in: CGRect(origin: CGPoint(x: rect.origin.x, y: 0), size: rect.size))
        self.restoreGState()
    }
}

Ви можете використовувати його точно як CGContextзвичайний draw(: in:)метод:

ctx.drawFlipped(myImage, in: myRect)

0

В ході мого проекту я підстрибнув від відповіді Кендалла в відповідь Кліффа , щоб вирішити цю проблему для зображень, які завантажуються з самого телефону.

Врешті-решт я закінчила використання CGImageCreateWithPNGDataProvider замість цього :

NSString* imageFileName = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"clockdial.png"];

return CGImageCreateWithPNGDataProvider(CGDataProviderCreateWithFilename([imageFileName UTF8String]), NULL, YES, kCGRenderingIntentDefault);

Це не страждає від проблем з орієнтацією, які ви отримаєте при отриманні CGImageвід a, UIImageі він може бути використаний як вміст CALayerбез зачіпки.


0
func renderImage(size: CGSize) -> UIImage {
    return UIGraphicsImageRenderer(size: size).image { rendererContext in
        // flip y axis
        rendererContext.cgContext.translateBy(x: 0, y: size.height)
        rendererContext.cgContext.scaleBy(x: 1, y: -1)

        // draw image rotated/offsetted
        rendererContext.cgContext.saveGState()
        rendererContext.cgContext.translateBy(x: translate.x, y: translate.y)
        rendererContext.cgContext.rotate(by: rotateRadians)
        rendererContext.cgContext.draw(cgImage, in: drawRect)
        rendererContext.cgContext.restoreGState()
    }
}

0

Швидка відповідь 5 основі чудової відповіді @ ZpaceZombor

Якщо у вас є UIImage, просто використовуйте

var image: UIImage = .... 
image.draw(in: CGRect)

Якщо у вас є CGImage, використовуйте мою категорію нижче

Примітка. На відміну від деяких інших відповідей, цей враховує, що пряма частина, яку ви хочете зробити, може мати y! = 0. Ті відповіді, які не враховують це, невірні і не працюватимуть у загальному випадку.

extension CGContext {
    final func drawImage(image: CGImage, inRect rect: CGRect) {

        //flip coords
        let ty: CGFloat = (rect.origin.y + rect.size.height)
        translateBy(x: 0, y: ty)
        scaleBy(x: 1.0, y: -1.0)

        //draw image
        let rect__y_zero = CGRect(x: rect.origin.x, y: 0, width: rect.width, height: rect.height)
        draw(image, in: rect__y_zero)

        //flip back
        scaleBy(x: 1.0, y: -1.0)
        translateBy(x: 0, y: -ty)

    }
} 

Використовуйте так:

let imageFrame: CGRect = ...
let context: CGContext = ....
let img: CGImage = ..... 
context.drawImage(image: img, inRect: imageFrame)

Це майже ідеальне рішення, але подумайте про зміну функції малювання (зображення: UIImage, inRect rect: CGRect) та обробляти uiImage.cgimage всередині методу
Марс

-5

Ви також можете вирішити цю проблему, зробивши це:

//Using an Image as a mask by directly inserting UIImageObject.CGImage causes
//the same inverted display problem. This is solved by saving it to a CGImageRef first.

//CGImageRef image = [UImageObject CGImage];

//CGContextDrawImage(context, boundsRect, image);

Nevermind... Stupid caching.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.