beginner lesson 14

Image data

Retrieving and using image data

Accessing image byte array data

When you want to start working with individual pixels, you can access this from individual layers or the document itself. The data format comes in a raw format called QByteArray. This format is a bit tricky to work with so let's go over what this is, and how to work with it for your images. Let's start by creating a simple 1x1 image and see what the data looks like.

from krita import *

# set up new document and set background color
newDocument = Krita.instance().createDocument(1, 1, "Document name", "RGBA", "U8", "", 300.0)
Krita.instance().activeWindow().addView(newDocument) # shows it in the application
activeNode = newDocument.activeNode()

# stored as QByteArray
# pixelData(x, y, width, height)
pixelBytes = activeNode.pixelData(0, 0, newDocument.width(), newDocument.height())

print(pixelBytes)

We will get this as an output from our print statement.

b'\xff\xff\xff\xff'

There is some characters wrapped in a b'....' string. The stuff inside there is the data we have to work with. Since we are only working with one pixel, the data will be easier to pick apart. Each \xff represents a number. In our case all that data translates to the white color. With the four parts, we can assume each number corresponds to red, green, blue, or the alpha channel.

To verify that the RGBA is what is being stored, let's do another test changing one of the colors. In the previous example the canvas color was white. Now, let's set our foreground color to red, fill in the canvas, then check to see if we see the changes being updated.

from krita import *

# set up new document and set background color
newDocument = Krita.instance().createDocument(1, 1, "Document name", "RGBA", "U8", "", 300.0)
activeView = Krita.instance().activeWindow().activeView()
Krita.instance().activeWindow().addView(newDocument) # shows it in the application

activeNode = newDocument.activeNode()
activeNode.setOpacity(255) # this does not change the pixel alpha data, just so our layer is visible


# create a new color in Python
# setting the foreground color needs a "Managed Color", so let's use that
colorRed = ManagedColor("RGBA", "U8", "")
colorComponents = colorRed.components()
colorComponents[0] = 1.0 # Red???
colorComponents[1] = 0.0 # Green???
colorComponents[2] = 0.0 # Blue???
colorComponents[3] = 1.0 # Alpha???
print(colorComponents) # the final values set
colorRed.setComponents(colorComponents)

# fill the canvas with our custom set color
activeView.setForeGroundColor(colorRed)
Krita.instance().action('fill_selection_foreground_color').trigger()
newDocument.refreshProjection() #update canvas on screen

# grab the byte array data and show the data that it is storing
pixelBytes = activeNode.pixelData(0, 0, newDocument.width(), newDocument.height())
print(pixelBytes)

And the output of the data looks like this in our Output

[1.0, 0.0, 0.0, 1.0]
b'\xff\x00\x00\xff'

The data looks right on the output. The values that are set to 1.0 are set to \xff. The values that are 0.0 are set to \x00. We would expect to the color to be red. If we look at the canvas though and the filled in color, we see blue.

RGB color selected

What is going on? If you play around with setting the values, you will notice that the red channel and the blue channel are flipped for some reason? Is this a bug? The reasoning is more of a historical reason with how image and color data were stored. BGR was once the standard format for working with images so many applications and standards had to support it. Either way, that is what we have to work with. As long as we know what is going on we will have to deal with it.

Converting to a better working image format and swapping BGRA to RGBA

So we kind of understand the data that is being saved and how two of the channels are being swapped. Byte arrays are difficult to work with, so let's convert this data to a better format. Converting it to a QImage will give us more power. We can then query specifiy pixels and grab color data.

from krita import *

# set up new document and set background color
newDocument = Krita.instance().createDocument(1, 1, "Document name", "RGBA", "U8", "", 300.0)
activeView = Krita.instance().activeWindow().activeView()
Krita.instance().activeWindow().addView(newDocument) # shows it in the application

activeNode = newDocument.activeNode()
activeNode.setOpacity(255) # this does not change the pixel alpha data, just so our layer is visible
newDocument.refreshProjection() #update canvas on screen

# grab the byte array data and show the data that it is storing
pixelBytes = activeNode.pixelData(0, 0, newDocument.width(), newDocument.height())
print(pixelBytes)


# QImage documentation - https://doc.qt.io/qt-5/qimage.html 
# QImage.Format_RGBA8888 - https://doc.qt.io/qt-5/qimage.html#Format-enum
imageData = QImage(pixelBytes, 1, 1, QImage.Format_RGBA8888)
pixelC = imageData.pixelColor(0,0)

print(imageData.depth())
print(pixelC.red(), pixelC.green(), pixelC.blue())

After converting the byte data to a QImage, it is much easier to work with now. One thing you will notice if you play around with the colors is that the red and blue values are still swapped. We could work knowing that, but there is also a helper function. We could swap out the QImage line to look like this from our previous example.

imageData = QImage(pixelBytes, 1, 1, QImage.Format_RGBA8888).rgbSwapped()

Now the channels are swapped. The downside of doing this is we will have to remember to swap it back later if we need to update the pixels on the image.

Note

Once we bring the byte array data and convert it to a Qimage, we are just working on the image data in memory. Changes we make will not affect what is on the layer. We will need to actually set the pixel data if we want to apply any changes.

Next Lesson >
Plugin Introduction

Have questions, need help, or find a typo?

Head to the Krita artists where we have a dedicated area for plugin development and give any feedback you might have.

;