2

In my project, textures are procedurally generated from method provided by PaintCode ().

I then create a SKTextureAtlas from a dictionary filed with UIImage generated by these methods :
myAtlas = SKTextureAtlas(dictionary: myTextures)

At last, textures are retrieve from atlas using textureNamed: var sprite1 = SKSpriteNode(texture:myAtlas.textureNamed("texture1"))

But displayed nodes are double sized on iPhone4S simulator. And triple sized on iPhone 6 Plus simulator.

It seems that at init, atlas compute images at the device resolution. But generated images already have the correct size and do not need to be changed. See Drawing Method below.

Here is the description of the generated image:
<UIImage: 0x7f86cae56cd0>, {52, 52}

And the description of the corresponding texture in atlas:
<SKTexture> 'image1' (156 x 156)

This for iPhone 6 Plus, using @3x images, that's why size is x3.

And for iPhone 4S, using @2x images, as expected:
<UIImage: 0x7d55dde0>, {52, 52}
<SKTexture> 'image1' (156 x 156)

At last, the scaleproperty for generated UIImage is set to the right device resolution: 2.0 for @2x (iPhone 4S) and 3.0 for @3x (iPhone 6 Plus).

The Question

So what can I do to avoid atlas resizing the pictures?

Drawing method

PaintCode generate drawing methods as the following:

public class func imageOfCell(#frame: CGRect) -> UIImage {
    UIGraphicsBeginImageContextWithOptions(frame.size, false, 0)
    StyleKit.drawCell(frame: frame)

    let imageOfCell = UIGraphicsGetImageFromCurrentImageContext()!
    UIGraphicsEndImageContext()

    return imageOfCell
}

Update 1

Comparing two approaches to generate SKTextureAtlas

// Some test image
let testImage:UIImage...


// Atlas creation
var myTextures = [String:UIImage]() 

myTextures["texture1"] = testImage
myAtlas = SKTextureAtlas(dictionary: myTextures)

// Create two textures from the same image 
let texture1 = myAtlas.textureNamed("texture1")
let texture2 = SKTexture(image:testImage)

// Wrong display : node is oversized
var sprite1 = SKSpriteNode(texture:texture1)

// Correct display
var sprite2 = SKSpriteNode(texture:texture2)

It seems that the problem lie on SKTextureAtlas from a dictionary as as SKSpriteNode initialization does not use scale property from UIImage to correctly size the node.

Here are descriptions on console: - texture1: '' (84 x 84) - texture2: 'texture1' (84 x 84)

texture2 miss some data! That could explain the lack of scale information to properly size the node as:

node's size = texture's size divide by texture's scale.

Update 2

The problem occur when the scale property of UIImage is different than one.

So you can use the following method to generate picture:

func imageOfCell(frame: CGRect, color:SKColor) -> UIImage {
     UIGraphicsBeginImageContextWithOptions(frame.size, false, 0)

     var bezierPath = UIBezierPath(rect: frame)
     color.setFill()
     bezierPath.fill()
     let imageOfCell = UIGraphicsGetImageFromCurrentImageContext()!
            UIGraphicsEndImageContext()

     return imageOfCell

    }

enter image description here

Dominique Vial
  • 3,729
  • 2
  • 25
  • 45

2 Answers2

2

The problem come from the use of SKTextureAtlas(dictionary:) to initialize atlas.

SKTexture created using this method does not embed data related to image's scale property. So during the creation of SKSpriteNode by init(texture:) the lack of scale information in texture leads to choose texture's size in place of image's size.

One way to correct it is to provide node's size during SKSpriteNode creation: init(texture:size:)

Dominique Vial
  • 3,729
  • 2
  • 25
  • 45
  • Aren't you creating a texture atlas that's larger than it needs to be (by 3x on the 6+)? – 0x141E Jul 22 '15 at 18:49
  • No. The only difference is the way textures are created. By the way, thank you for your help! – Dominique Vial Jul 22 '15 at 22:00
  • You can try to obtain the same behavior by creating another sprite using the same generated image but texture created with this image without atlas. And don't forget to call `imageOfCell` with a scale's value of 0. – Dominique Vial Jul 22 '15 at 22:02
  • If you retrieve a texture from an atlas and then print its size, with `println (texture.size())`, I'm pretty sure that's the size of the texture in the atlas. I suspect what you are doing is resizing the sprite when you create it with `init(texture:size)`. – 0x141E Jul 22 '15 at 22:13
  • The problem is the size of the `SKSpriteNode`, not the size of the texture. Just try the modification I provided you above and you'll see that depending on how the texture is created the `SKSpriteNode` is not rendered the same way. – Dominique Vial Jul 22 '15 at 22:33
  • If you set `scale = 1.0`, the texture size is the correct size, right? And then your sprite will be the right size. – 0x141E Jul 22 '15 at 22:38
  • Hum. It seem my precedent comments have not been understood. So in other words. The problem is that depending on how texture are generated the _scale_ property of `UIImage` is used or not. – Dominique Vial Jul 22 '15 at 22:45
  • So it is not relevant to change the _scale_ property because this data is relevant and important. This answer give a workaround to the texture atlas generation bug. – Dominique Vial Jul 22 '15 at 22:52
  • By the way I add an __update 2__ to question with screenshot – Dominique Vial Jul 22 '15 at 22:54
  • The `imageOfCell` method is generated by PaintCode and directly inserted into the code each time the PaintCode drawing is modified. The way it handle `scale`cannot be changed but tweaked manually after each generation. Impractical. – Dominique Vial Jul 22 '15 at 23:03
0

From the documentation for the scale parameter for UIGraphicsBeginImageContextWithOptions,

The scale factor to apply to the bitmap. If you specify a value of 0.0, the scale factor is set to the scale factor of the device’s main screen.

Therefore, if you want the textures to be the same "size" across all devices, set this value to 1.0.

EDIT:

override func didMoveToView(view: SKView) {        
    let image = imageOfCell(CGRectMake(0, 0, 10, 10),scale:0)

    let dict:[String:UIImage] = ["t1":image]

    let texture = SKTextureAtlas(dictionary: dict)
    let sprite1 = SKSpriteNode(texture: texture.textureNamed("t1"))
    sprite1.position = CGPointMake (CGRectGetMidX(view.frame),CGRectGetMidY(view.frame))
    addChild(sprite1)
    println(sprite1.size)

    // prints (30.0, 30.0) if scale = 0
    // prints (10,0, 10,0) if scale = 1
}


func imageOfCell(frame: CGRect, scale:CGFloat) -> UIImage {
    UIGraphicsBeginImageContextWithOptions(frame.size, false, scale)

    var bezierPath = UIBezierPath(rect: frame)
    UIColor.whiteColor().setFill()
    bezierPath.fill()
    let imageOfCell = UIGraphicsGetImageFromCurrentImageContext()!
    UIGraphicsEndImageContext()

    return imageOfCell
}
0x141E
  • 12,613
  • 2
  • 41
  • 54
  • The problem lie in the texture generation not in the image generation using `UIGraphicsBeginImageContextWithOptions`. – Dominique Vial Jul 22 '15 at 08:58
  • `UIGraphicsBeginImageContextWithOptions` do the job by setting the correct value, the one of the device's main screen. – Dominique Vial Jul 22 '15 at 08:58
  • From the `UIImage` documentation: _If you load an image from a file whose name includes the @2x modifier, the scale is set to 2.0. You can also specify an explicit scale factor when initializing an image from a Core Graphics image. All other images are assumed to have a scale factor of 1.0._... – Dominique Vial Jul 22 '15 at 08:59
  • _If you multiply the logical size of the image (stored in the size property) by the value in this property, you get the dimensions of the image in pixels_ – Dominique Vial Jul 22 '15 at 09:02
  • `SKTextureAtlas` uses the `scale` property of a `UIImage` when generating a texture. If an image is 10x10 and its scale is 3, the resulting texture will be 30x30. – 0x141E Jul 22 '15 at 11:10
  • `SKTextureAtlas` does not seem to use the `scale` property when it is generated using a dictionary. – Dominique Vial Jul 22 '15 at 14:22
  • Which version of Xcode are you running? Texture and texture atlas use `scale` in the same way with 6.4. – 0x141E Jul 22 '15 at 14:58
  • Let's try on you side using the code I post on __Update 1__. – Dominique Vial Jul 22 '15 at 15:01
  • And then display the new sprite's size. – Dominique Vial Jul 22 '15 at 15:27
  • It seems `SKTextureAtlas(dictionary:)` destroy data in dictionary! So you have to create `sprite2` before calling ``SKTextureAtlas(dictionary:)`! – Dominique Vial Jul 22 '15 at 15:30