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

begin / end CGcontextメソッドの途中。

これにより、正しい方向の画像が現在の画像コンテキストに描画されます。これはUIImageCGContextDrawImage方法が方向を理解せずに基になる生の画像データを取得する間、方向の知識を保持することと関係があると確信しています。


19
このソリューションでは、CGContextSetAlpha()などの画像でCG関数を使用できませんが、2番目の答えは使用できます。
samvermette、

2
良い点ですが、ほとんどの場合、イメージを描画するためにカスタムアルファレベルは必要ありません(通常、アルファを必要とするもののために事前にイメージに焼き付けられます)。基本的に私のモットーは、コードを増やすとバグが発生する可能性が高くなるため、できる限り小さなコードを使用することです。
Kendall Helmstetter Gelner、2010

こんにちは、CGContextメソッドの開始/終了の途中でどういう意味ですか。サンプルコードをもう少し教えてもらえますか。コンテキストをどこかに保存して、コンテキストを使用して画像を描画しようとしています
vodkhang

2
Rustyでは機能する可能性がありますが、-[UIImage drawInRect:]にはスレッドセーフではないという問題があります。私の特定のアプリケーションでは、そもそもCGContextDrawImage()を使用している理由です。
Olie

2
明確にするために:Core Graphicsは、iOSと比較して座標系が上下逆になっているOS Xに組み込まれています(OS Xの左下から開始)。そのため、Core Graphicsは画像を上下逆さまに描画しますが、drawInRectはこれを処理します
borchero

213

私が述べたすべてを適用した後でも、私はまだ画像のドラマを持っています。最後に、私はGimpを使用して、すべての画像の「上下反転」バージョンを作成しました。これで、トランスフォームを使用する必要がなくなりました。うまくいけば、これは将来の問題をさらに引き起こさないでしょう。

CGContextDrawImageが私の画像を逆さまに描画する理由を誰かが知っていますか?アプリケーションから画像を読み込んでいます:

Quartz2dは、原点が左下隅にある別の座標系を使用します。したがって、Quartzが100 x 100の画像のピクセルx [5]、y [10]を描画すると、そのピクセルは左上ではなく左下隅に描画されます。したがって、「反転」イメージが発生します。

x座標系が一致するため、y座標を反転する必要があります。

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

これは、x軸を0単位、y軸を画像の高さとして画像を平行移動したことを意味します。ただし、これだけでは画像が上下逆になり、描画したい場所の下に "image.size.height"が描画されるだけです。

Quartz2Dプログラミングガイドでは、ScaleCTMを使用し、負の値を渡して画像を反転させることを推奨しています。これを行うには、次のコードを使用できます-

CGContextScaleCTM(context, 1.0, -1.0);

CGContextDrawImage呼び出しの直前に2つを組み合わせると、画像が正しく描画されます。

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ではなくUIImageを使用していることに気付きました。ここで使用している下位レベルの
CGImageもの

3
drawLayerを使用していて、CGImageRefをコンテキストに描画したい場合、ここでのこのテクニックは、医師に注文したいだけです!画像の四角形がある場合は、CGContextTranslateCTM(context、0、image.size.height + image.origin.y)、次にCGContextDrawImage()の前にrect.origin.yを0に設定します。ありがとう!
David H

かつてImageMagickで問題がありましたが、iPhoneカメラの向きが正しくありませんでした。それは画像ファイルの方向フラ​​グであることがわかります。したがって、縦向きの画像は、たとえば960px x 640pxで、フラグが「左」に設定されています-左側が上(またはそれに近いもの)なので このスレッドは役立つかもしれません:stackoverflow.com/questions/1260249/…–
Hari Karam Singh

8
を使用CGContextSaveGState()CGContextRestoreGState()て、変換マトリックスを保存および復元しませんか?
ジャック・

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-SW4

デフォルトの座標系を反転する

UIKit図面を反転すると、バッキングCALayerが変更され、LLO座標系を持つ描画環境がUIKitのデフォルトの座標系に合わせられます。描画にUIKitメソッドと関数のみを使用する場合は、CTMを反転する必要はありません。ただし、Core GraphicsまたはImage I / O関数呼び出しをUIKit呼び出しと混在させる場合は、CTMの切り替えが必要になる場合があります。

特に、Core Graphics関数を直接呼び出して画像またはPDFドキュメントを描画する場合、オブジェクトはビューのコンテキストで上下逆にレンダリングされます。画像とページを正しく表示するには、CTMを反転させる必要があります。

コアグラフィックスコンテキストに描画されたオブジェクトを反転して、UIKitビューに表示したときに正しく表示されるようにするには、2つの手順で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

コンテキスト内のカスタムrectに画像を描画するための簡単なソリューションに興味がある場合:

 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

UIImageCGImageメインコンテンツメンバーとして含み、スケーリングと方向付けの要素を含みます。以来、CGImageその様々な機能はOSXから誘導され、それが上下逆さまにiPhoneと比較される座標系を想定しています。を作成するとUIImage、デフォルトで上下を逆にして補正します(これは変更できます!)。使用 。CGImageプロパティを使用して非常に強力なCGImage機能にアクセスできますが、iPhone画面などに描画するにはUIImageメソッドを使用するのが最適です。


1
UIImageのデフォルトの上下の向きをどのように変更しますか?
MegaManX 2013

3

Swiftコードによる補足回答

Quartz 2Dグラフィックは左下を原点とする座標系を使用しますが、iOSのUIKitは左上を原点とする座標系を使用します。通常はすべて正常に動作しますが、一部のグラフィックス操作を行う場合は、自分で座標系を変更する必要があります。ドキュメントの状態:

一部のテクノロジーは、Quartzで使用されるものとは異なるデフォルトの座標系を使用してグラフィックスコンテキストを設定します。Quartzに比べて、このような座標系は修正された座標系であり、一部のQuartz描画操作を実行するときに補正する必要があります。最も一般的な修正された座標系は、原点をコンテキストの左上隅に配置し、y軸をページの下部を指すように変更します。

この現象は、drawRectメソッドで画像を描画するカスタムビューの次の2つのインスタンスで確認できます。

ここに画像の説明を入力してください

左側では、画像が上下逆になっており、右側では、原点が左上になるように座標系が変換およびスケーリングされています。

逆さの画像

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確かに行く方法です。これを行う際に役立つもう1つの小さなことを次に示します。通常、画像とそれが入る長方形は一致しません。その場合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ソリューション

UIImageの代わりに、何らかの理由でCGを使用したい場合は、以前の回答に基づいたこの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

私はこの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 から取得することで得られる向きの問題の影響を受けず、問題なく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

@ZpaceZomborの優れた回答に基づくSwift 5の回答

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.
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.