CGContextDrawImage desenha a imagem de cabeça para baixo quando passou UIImage.CGImage

179

Alguém sabe por CGContextDrawImageque estaria desenhando minha imagem de cabeça para baixo? Estou carregando uma imagem do meu aplicativo:

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

E, em seguida, basta pedir aos gráficos principais para atraí-lo para o meu contexto:

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

É renderizado no lugar certo e nas dimensões, mas a imagem está de cabeça para baixo. Eu devo estar perdendo algo realmente óbvio aqui?

enferrujado
fonte

Respostas:

238

Ao invés de

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

Usar

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

No meio de seus CGcontextmétodos de início / fim .

Isso atrairá a imagem com a orientação correta para o seu contexto de imagem atual - tenho certeza de que isso tem algo a ver com o UIImageconhecimento da orientação enquanto o CGContextDrawImagemétodo obtém os dados brutos da imagem subjacente sem entender a orientação.

Kendall Helmstetter Gelner
fonte
19
Esta solução não permite que você use funções CG em sua imagem, como CGContextSetAlpha (), enquanto a segunda resposta o faz.
Samvermette 18/05
2
Bom ponto, embora na maioria das vezes você não precise de níveis alfa personalizados para desenhar uma imagem (normalmente eles são incorporados em imagens antecipadamente para itens que precisam de alfa). Basicamente, meu lema é: use um pouco de código possível, pois mais código significa mais chances de erros.
Kendall Helmstetter Gelner
Oi, o que você quer dizer com no meio dos métodos CGContext de início / fim, você pode me dar mais código de exemplo. Estou tentando com armazenar a algum lugar contexto e usar o contexto para desenhar a imagem em
vodkhang
2
Embora possa funcionar para Rusty, - [UIImage drawInRect:] tem o problema de que não é seguro para threads. Para meu aplicativo em particular, é por isso que estou usando CGContextDrawImage () em primeiro lugar.
Olie
2
Apenas para esclarecer: o Core Graphics é construído no OS X, onde o sistema de coordenadas está de cabeça para baixo em comparação com o iOS (y começando no lado inferior esquerdo do OS X). É por isso Core Graphics desenha a imagem de cabeça para baixo enquanto drawInRect lida com isso para você
borchero
213

Mesmo depois de aplicar tudo o que mencionei, ainda tive dramas com as imagens. No final, acabei de usar o Gimp para criar uma versão 'invertida vertical' de todas as minhas imagens. Agora não preciso usar nenhuma transformação. Espero que isso não cause mais problemas na pista.

Alguém sabe por que CGContextDrawImage estaria desenhando minha imagem de cabeça para baixo? Estou carregando uma imagem do meu aplicativo:

O Quartz2d usa um sistema de coordenadas diferente, onde a origem está no canto inferior esquerdo. Portanto, quando o Quartz desenha o pixel x [5], y [10] de uma imagem 100 * 100, esse pixel está sendo desenhado no canto inferior esquerdo em vez de no canto superior esquerdo. Assim, causando a imagem 'invertida'.

O sistema de coordenadas x corresponde, então você precisará inverter as coordenadas y.

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

Isso significa que traduzimos a imagem em 0 unidades no eixo x e na altura das imagens no eixo y. No entanto, isso por si só significará que nossa imagem ainda está de cabeça para baixo, apenas sendo desenhada "image.size.height" abaixo de onde desejamos que ela seja desenhada.

O guia de programação do Quartz2D recomenda o uso do ScaleCTM e a passagem de valores negativos para inverter a imagem. Você pode usar o seguinte código para fazer isso -

CGContextScaleCTM(context, 1.0, -1.0);

Combine os dois antes da sua CGContextDrawImageligação e você deverá ter a imagem desenhada corretamente.

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);

Apenas tome cuidado se as coordenadas do imageRect não corresponderem às da sua imagem, pois você pode obter resultados indesejados.

Para converter de volta as coordenadas:

CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0, -imageRect.size.height);
Cliff Viegas
fonte
isso é bom, mas descobri que outras coisas estavam sendo influenciadas pela conversão e escala, mesmo quando tentei voltar para 0,0 e 1,0,1.0 Fui com a solução do Kendall no final, mas percebo que isso está usando o UIImage em vez do material de CGImage de nível inferior com o qual estamos trabalhando aqui
PeanutPower
3
Se você estiver usando o drawLayer e quiser desenhar um CGImageRef no contexto, esta técnica aqui é apenas o que o médico pediu! Se tiver o rect para a imagem, CGContextTranslateCTM (contexto, 0, image.size.height + image.origin.y), defina o rect.origin.y como 0 antes de CGContextDrawImage (). Obrigado!
19712 David H
Uma vez tive problemas com o ImageMagick, em que a câmera do iPhone resultou em orientação incorreta. Acontece que é o sinalizador de orientação no arquivo de imagem, então as imagens de retrato eram, digamos 960px x 640px com o sinalizador definido como "esquerdo" - como no lado esquerdo está o topo (ou algo próximo disso). Este tópico pode ajudar: stackoverflow.com/questions/1260249/…
Hari Karam Singh
8
Por que você não usa CGContextSaveGState()e CGContextRestoreGState()para salvar e restaurar a matriz de transformação?
Ja͢ck
20

Melhor dos dois mundos, use UIImage drawAtPoint:ou drawInRect:enquanto especifica seu contexto personalizado:

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

Além disso, evite modificar seu contexto com CGContextTranslateCTMou com o CGContextScaleCTMque a segunda resposta o faz.

Rivera
fonte
Essa é uma ótima idéia, mas para mim resultou em uma imagem que estava de cabeça para baixo e da esquerda para a direita. Suponho que os resultados variam de imagem para imagem.
Luke Rogers
Talvez isso tenha parado de funcionar com versões posteriores do iOS? Na época, funcionava com tudo para mim.
Rivera
Eu acho que foi realmente como eu estava criando o contexto. Depois que comecei a usar, em UIGraphicsBeginImageContextWithOptionsvez de apenas introduzir um novo CGContext, parecia funcionar.
Luke Rogers
12

Documentos relevantes do Quartz2D: https://developer.apple.com/library/ios/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GraphicsDrawingOverview/GraphicsDrawingOverview.html#//apple_ref/doc/uid/TP40010156-CH14-SW4

Invertendo o sistema de coordenadas padrão

Inverter o desenho do UIKit modifica o CALayer de suporte para alinhar um ambiente de desenho com um sistema de coordenadas LLO com o sistema de coordenadas padrão do UIKit. Se você usar apenas métodos e funções do UIKit para desenhar, não precisará inverter o CTM. No entanto, se você combinar chamadas de função Core Graphics ou Image I / O com chamadas UIKit, poderá ser necessário inverter o CTM.

Especificamente, se você desenhar uma imagem ou documento PDF chamando diretamente as funções de Gráficos principais, o objeto será renderizado de cabeça para baixo no contexto da exibição. Você deve virar o CTM para exibir a imagem e as páginas corretamente.

Para inverter um objeto desenhado em um contexto de Gráficos principais para que ele apareça corretamente quando exibido em uma visualização UIKit, você deve modificar o CTM em duas etapas. Você converte a origem no canto superior esquerdo da área de desenho e aplica uma conversão de escala, modificando a coordenada y por -1. O código para fazer isso é semelhante ao seguinte:

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

Se alguém estiver interessado em uma solução simples para desenhar uma imagem em um ret personalizado em um contexto:

 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)

}

A imagem será redimensionada para preencher o retângulo.

ZpaceZombor
fonte
7

Não tenho certeza UIImage, mas esse tipo de comportamento geralmente ocorre quando as coordenadas são invertidas. A maioria dos sistemas de coordenadas do OS X tem sua origem no canto inferior esquerdo, como em Postscript e PDF. Mas o CGImagesistema de coordenadas tem sua origem no canto superior esquerdo.

As possíveis soluções podem envolver uma isFlippedpropriedade ou uma scaleYBy:-1transformação afim.

mouviciel
fonte
3

UIImagecontém um CGImagemembro de conteúdo principal e fatores de dimensionamento e orientação. Como CGImagee suas várias funções são derivadas do OSX, ele espera um sistema de coordenadas de cabeça para baixo em comparação com o iPhone. Quando você cria um UIImage, o padrão é uma orientação invertida para compensar (você pode alterar isso!). Use o . CGImagepropriedade para acessar CGImagefunções muito poderosas , mas é melhor desenhar na tela do iPhone etc. com os UIImagemétodos.

James L
fonte
1
como você altera a orientação invertida padrão do UIImage?
MegaManX
3

Resposta suplementar com código Swift

Os gráficos 2D de quartzo usam um sistema de coordenadas com a origem no canto inferior esquerdo, enquanto o UIKit no iOS usa um sistema de coordenadas com a origem no canto superior esquerdo. Geralmente, tudo funciona bem, mas ao fazer algumas operações gráficas, você deve modificar o sistema de coordenadas. A documentação declara:

Algumas tecnologias configuram seus contextos gráficos usando um sistema de coordenadas padrão diferente daquele usado pelo Quartz. Em relação ao Quartzo, esse sistema de coordenadas é um sistema de coordenadas modificado e deve ser compensado ao executar algumas operações de desenho de quartzo. O sistema de coordenadas modificado mais comum coloca a origem no canto superior esquerdo do contexto e altera o eixo y para apontar para a parte inferior da página.

Esse fenômeno pode ser visto nas duas instâncias a seguir de visualizações personalizadas que desenham uma imagem em seus drawRectmétodos.

insira a descrição da imagem aqui

No lado esquerdo, a imagem está de cabeça para baixo e no lado direito o sistema de coordenadas foi traduzido e dimensionado para que a origem esteja no canto superior esquerdo.

Imagem de cabeça para baixo

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)

}

Sistema de coordenadas modificado

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)
}
Suragch
fonte
3

Swift 3.0 e 4.0

yourImage.draw(in: CGRect, blendMode: CGBlendMode, alpha: ImageOpacity)

Nenhuma alteração necessária

Abdul Waheed
fonte
2

Podemos resolver esse problema usando a mesma função:

UIGraphicsBeginImageContext(image.size);

UIGraphicsPushContext(context);

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

UIGraphicsPopContext();

UIGraphicsEndImageContext();
Priyanka V
fonte
1

drawInRecté certamente o caminho a percorrer. Aqui está outra coisinha que será útil ao fazer isso. Normalmente, a figura e o retângulo com o qual vai se encaixar não se adaptam. Nesse caso drawInRect, esticará a imagem. Aqui está uma maneira rápida e bacana de garantir que a proporção da imagem não seja alterada, revertendo a transformação (que será adequada para tudo):

//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];
Marc Meyer
fonte
1

Solução Swift 3 CoreGraphics

Se você deseja usar o CG por qualquer motivo, em vez de UIImage, essa construção do Swift 3 com base nas respostas anteriores resolveu o problema para mim:

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()
}
biomiker
fonte
1

Isso acontece porque o QuartzCore possui o sistema de coordenadas "inferior esquerdo", enquanto o UIKit - "superior esquerdo".

No caso, você pode estender o 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)
dimpiax
fonte
1

Eu uso esta extensão pura do Swift 5 , Core Graphics, que lida corretamente com origens diferentes de zero em rects de imagem:

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()
    }
}

Você pode usá-lo exatamente como CGContexto draw(: in:)método regular :

ctx.drawFlipped(myImage, in: myRect)
Robin Stewart
fonte
0

Durante o curso do meu projeto, pulei da resposta de Kendall para a resposta de Cliff para resolver esse problema para imagens carregadas do próprio telefone.

No final, acabei usando CGImageCreateWithPNGDataProvider:

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

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

Isso não sofre com os problemas de orientação que você obteria com o CGImagea UIImagee pode ser usado como o conteúdo de um CALayersem problemas.

Ja͢ck
fonte
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()
    }
}
neoneye
fonte
0

Resposta rápida 5 baseada na excelente resposta do @ ZpaceZombor

Se você possui uma UIImage, basta usar

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

Se você tem uma CGImage, use minha categoria abaixo

Nota: Diferentemente de outras respostas, esta leva em consideração que o texto que você deseja extrair pode ter y! = 0. Essas respostas que não levam isso em consideração estão incorretas e não funcionam no caso geral.

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)

    }
} 

Use assim:

let imageFrame: CGRect = ...
let context: CGContext = ....
let img: CGImage = ..... 
context.drawImage(image: img, inRect: imageFrame)
n13
fonte
Essa é quase uma solução perfeita, mas considere alterar a função para desenhar (image: UIImage, inRect rect: CGRect) e manipular uiImage.cgimage dentro do método
Mars
-5

Você também pode resolver esse problema fazendo o seguinte:

//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.
Ben Zeeman
fonte