How to force SKTextureAtlas created from a dictionary to not modify textures size?
Asked Answered
P

2

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

Psychomancy answered 22/7, 2015 at 8:3 Comment(1)
Problem still here in iOS 9 / Xcode 7...Psychomancy
P
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:)

Psychomancy answered 22/7, 2015 at 17:25 Comment(10)
Aren't you creating a texture atlas that's larger than it needs to be (by 3x on the 6+)?Vena
No. The only difference is the way textures are created. By the way, thank you for your help!Psychomancy
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.Psychomancy
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).Vena
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.Psychomancy
If you set scale = 1.0, the texture size is the correct size, right? And then your sprite will be the right size.Vena
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.Psychomancy
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.Psychomancy
By the way I add an update 2 to question with screenshotPsychomancy
The imageOfCell method is generated by PaintCode and directly inserted into the code each time the PaintCode drawing is modified. The way it handle scalecannot be changed but tweaked manually after each generation. Impractical.Psychomancy
V
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
}
Vena answered 22/7, 2015 at 8:47 Comment(10)
The problem lie in the texture generation not in the image generation using UIGraphicsBeginImageContextWithOptions.Psychomancy
UIGraphicsBeginImageContextWithOptions do the job by setting the correct value, the one of the device's main screen.Psychomancy
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....Psychomancy
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 pixelsPsychomancy
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.Vena
SKTextureAtlas does not seem to use the scale property when it is generated using a dictionary.Psychomancy
Which version of Xcode are you running? Texture and texture atlas use scale in the same way with 6.4.Vena
Let's try on you side using the code I post on Update 1.Psychomancy
And then display the new sprite's size.Psychomancy
It seems SKTextureAtlas(dictionary:) destroy data in dictionary! So you have to create sprite2 before calling ``SKTextureAtlas(dictionary:)`!Psychomancy

© 2022 - 2024 — McMap. All rights reserved.