Layer, Bitmap, and ImageFunction¶
Kirikiri Z has two classes for handling images, Layer and Bitmap, plus ImageFunction, a class made up only of methods. This page explains how the three relate and which one to use when.
In three lines¶
- Layer is "an image that is displayed". Besides the image buffer, it has a parent-child tree, display position, opacity, input events, and redraw notifications.
- Bitmap is "a single image buffer that is not displayed". It can load, save, and access pixels, but has no drawing methods at all.
- ImageFunction is a collection of static methods that can be called directly on a Bitmap, made by stripping the Layer-specific concerns (display attributes, clip rectangle, update notifications) from Layer's drawing methods. It exists to fill the gap left by Bitmap having no drawing methods.
Structure¶
TJS2 sees three classes, but there is only one kind of engine-internal object (tTVPBaseBitmap) that actually holds and processes the pixels.
Layer and Bitmap simply wrap it, and the drawing algorithms (Blt / StretchBlt / AffineBlt / fill / text drawing ...) are completely shared.
TJS2 level
┌────────────────────────────────┐ ┌──────────────────────────────┐ ┌───────────────────────┐
│ Layer │ │ Bitmap │ │ ImageFunction │
│ - parent-child tree/position │ │ - just one image │ │ - all methods static │
│ - visible / opacity / type │ │ - width / height / buffer │ │ - no point in │
│ - face / holdAlpha / clip* │ │ - load / loadAsync / save │ │ creating instances │
│ - input events / focus │ │ - getPixel / setPixel │ │ │
│ - many drawing methods │ │ - no drawing methods of its │ │ │
│ - drawing triggers update() │ │ own │ │ │
└───────────────┬────────────────┘ └───────────────┬──────────────┘ └───────────┬───────────┘
│ MainImage │ │
│ ProvinceImage (province image) │ (just one) │ takes a Bitmap
▼ ▼ │ as 1st argument
C++ level ┌───────────────────────────────────────────────────────────┐ │
│ tTVPBaseBitmap │◀──────────┘
│ the pixel buffer (32bpp / 8bpp, copy-on-write sharing) │
│ Blt / StretchBlt / AffineBlt / Fill / DrawText / Blur … │
└───────────────────────────────────────────────────────────┘
In other words, ImageFunction is "the Bitmap version of Layer's drawing methods". The processing called internally is exactly the same as the Layer version; the only difference is "who decides the arguments".
Why Bitmap and ImageFunction are needed¶
A Layer cannot be created on its own. Its constructor needs a Window (or a
BitmapLayerTreeOwner) and a parent layer,
every drawing operation triggers a redraw-region notification (update), and it becomes part of the whole tree's compositing.
That is too heavy for "images you want to process without showing them" — asset preprocessing, caches, screenshot processing, asynchronous loading.
Bitmap is the lightweight container for that: it is created with just new Bitmap(w, h) and has nothing to do with display or trees.
However, Bitmap itself has no drawing methods, so ImageFunction is provided as the way to composite, draw text, and apply filters on a Bitmap.
Some features exist only on Bitmap. The main one is asynchronous image loading (Bitmap.loadAsync); Layer has no corresponding method. The usual approach is to load into a Bitmap and then pass it to a Layer with Layer.copyFromBitmapToMainImage.
Method correspondence¶
| Operation | Layer | ImageFunction (target is a Bitmap) | Bitmap itself |
|---|---|---|---|
| Rectangle fill | fillRect |
fillRect |
— |
| Translucent / mask fill | colorRect |
colorRect |
— |
| Text drawing | drawText |
drawText (Font given explicitly) |
— |
| Glyph drawing | drawGlyph |
drawGlyph |
— |
| Shaped text drawing | drawShapedText / drawShapedTextArea |
none | — |
| Vertical text drawing | drawVerticalTextArea |
none | — |
| Rectangle copy | copyRect |
operateRect + omOpaque / dfOpaque |
— |
| Rectangle blend | operateRect |
operateRect |
— |
| Scaled copy | stretchCopy |
operateStretch + omOpaque / dfOpaque |
— |
| Scaled blend | operateStretch |
operateStretch |
— |
| Affine copy | affineCopy |
operateAffine + omOpaque / dfOpaque |
— |
| Affine blend | operateAffine |
operateAffine |
— |
| 9-patch copy | copy9Patch |
copy9Patch |
— |
| Box blur | doBoxBlur |
doBoxBlur |
— |
| Gamma correction | adjustGamma |
adjustGamma |
— |
| Grayscale | doGrayScale |
doGrayScale |
— |
| Horizontal / vertical flip | flipLR / flipUD |
flipLR / flipUD |
— |
| Tree composite copy | piledCopy |
none (no concept of child layers) | — |
| Pixel read/write | getMainPixel / setMainPixel / getMaskPixel / setMaskPixel |
— | getPixel / setPixel / getMaskPixel / setMaskPixel |
| Image loading | loadImages |
— | load / loadAsync / loadHeader |
| Image saving | saveLayerImage |
— | save |
| Unsharing | independMainImage |
— | independ |
| Image resizing | setImageSize |
— | setSize |
Why ImageFunction has no copy methods
ImageFunction has no dedicated methods corresponding to copyRect / stretchCopy / affineCopy.
This is because operate* with mode = omOpaque, face = dfOpaque, hda = false is already a plain copy (bmCopy).
However, there is no equivalent of the clear argument of affineCopy (which clears the area outside the transformed region).
Values Layer uses implicitly are all explicit arguments in ImageFunction¶
For the same drawing operation, the Layer version builds its arguments from its own properties. Bitmap has none of those properties, so with ImageFunction the caller passes everything explicitly. This is where discrepancies tend to appear when porting.
| What Layer uses implicitly | ImageFunction equivalent |
|---|---|
| Layer.face | face argument (default dfAlpha) |
| Layer.holdAlpha | hda argument (default false) |
| Layer.clipLeft and the rest of the clip rectangle | cliprect argument (default is the whole image; null means no clipping) |
| Layer.font | The font argument of drawText. A Font object is required (can be created standalone with new Font()) |
Resolution of omAuto from the source Layer.type |
There is nothing to resolve from, so omAuto always falls back to omAlpha |
Redraw notification via update() after drawing |
None (not needed, since a Bitmap is not displayed) |
| Layer.imageModified | None |
How omAuto is handled
omAuto means "decide the blend method from the type property of the source Layer".
Bitmap has no type, so in ImageFunction omAuto unconditionally becomes omAlpha.
The same happens when you pass a Bitmap as the source to Layer.operateRect and similar methods
(omAuto is treated as omAlpha in that case too). If you want additive or screen blending, specify mode explicitly.
Moving between Layer and Bitmap¶
Passing a Bitmap to Layer drawing methods (1.1.0 and later)¶
The following Layer methods accept either a Layer or a Bitmap as the copy / blend source (src):
copyRect / copy9Patch / operateRect / stretchCopy / operateStretch / affineCopy / operateAffine
When you keep assets as Bitmaps and draw them into a Layer, this is the method with the fewest steps.
Note that piledCopy (composite copy including child layers) and assignImages are Layer-only and do not accept a Bitmap.
Layer → Bitmap¶
var bmp = new Bitmap();
layer.copyToBitmapFromMainImage(bmp); // メイン画像を bmp へ
Internally this only sets up copy-on-write sharing, so no actual pixel copy happens and it is fast (the two are separated automatically as soon as either is written to). The 32bpp image including the mask (alpha) is passed as is, but the province image is not passed.
Bitmap → Layer¶
layer.copyFromBitmapToMainImage(bmp); // bmp をレイヤのメイン画像に
This also uses copy-on-write sharing, but on the Layer side it has these side effects:
- The layer's image size is changed to the Bitmap's size
- The clip rectangle is reset
updateruns and the layer is redrawn
If you want to replace only part of the image while keeping the layer's image size,
pass the Bitmap to copyRect / operateRect instead.
ImageFunction does not accept a Layer¶
Every ImageFunction method accepts only Bitmap objects. Passing a Layer raises an invalid-argument exception.
To process a Layer's image with ImageFunction, first receive it into a Bitmap with copyToBitmapFromMainImage,
then write it back with copyFromBitmapToMainImage after processing.
Getting the composited result of a layer tree as a Bitmap¶
To build a layer tree without a Window and get its composited result as a Bitmap,
use BitmapLayerTreeOwner.
Pass it as the first argument of the Layer constructor instead of a Window, and the tree's rendered result is available
as a Bitmap from BitmapLayerTreeOwner.bitmap.
Pitfalls¶
Nothing is redrawn : A Bitmap is not a display element, so drawing into it with ImageFunction changes nothing on screen. It becomes visible only after you transfer it to a Layer.
Copy-on-write sharing
: Bitmap.copyFrom / assignImages / copyToBitmapFromMainImage and similar only share the actual buffer.
Normally they are separated automatically right before a write, so you need not think about it, but if you know you will overwrite the whole image,
passing false to Bitmap.independ to separate them first avoids a wasted copy.
Direct buffer access
: Bitmap.buffer is a read-only pointer; to write, use
Bitmap.bufferForWrite (sharing is released as soon as you read it).
Bitmap.bufferPitch can be negative (bottom-up storage).
Bitmap also has alias properties with the same names as Layer's (imageWidth / imageHeight / mainImageBuffer /
mainImageBufferForWrite / mainImageBufferPitch), so plugins and DrawDevices can get images
without distinguishing between Layer and Bitmap.
8bpp Bitmaps
: new Bitmap(w, h, 8) creates an 8bpp image, but operations that assume color and alpha
(the operate* family, some uses of fillRect, etc.) throw exceptions on 8bpp. 8bpp is intended for province-image-like uses.
Destroying the alpha channel
: Arithmetic blends such as multiply, add, and screen do not protect the destination alpha when hda (= Layer's holdAlpha) is false.
When blending onto an image with alpha (as with ltAlpha), specify hda = true.
Forgetting it leads to symptoms such as the result turning completely black (alpha becoming 0).
Shaped text drawing is Layer-only
: The drawShapedText / drawShapedTextArea / measureShapedText family, and the vertical-text
drawVerticalTextArea / measureVerticalTextArea, exist only on Layer.
If you need complex text layout on a Bitmap, draw on a Layer first and then receive the result into a Bitmap.
Typical patterns¶
Composite assets off screen, then pass them to a Layer¶
// 640x480 の作業用 Bitmap を作り、不透明な黒でクリア
var work = new Bitmap(640, 480);
ImageFunction.fillRect(work, 0xff000000); // isalpha=true なので 0xAARRGGBB
// 背景素材を単純コピー (copyRect 相当)
var bg = new Bitmap("bg.png");
ImageFunction.operateRect(work, 0, 0, bg, null, null, omOpaque, dfOpaque);
// 文字を載せる (Font は明示的に用意する)
var font = new Font();
font.face = "IPAGothic";
font.height = 24;
ImageFunction.drawText(work, font, 16, 16, "スコア: 1200", 0xffffff);
// できあがった画像をレイヤへ (ここで初めて画面に反映される)
layer.copyFromBitmapToMainImage(work);
Load an image asynchronously and feed it into a layer¶
class AsyncImage extends Bitmap {
var target;
function AsyncImage(target) {
super.Bitmap();
this.target = target;
}
function onLoaded(meta, async, error, message) {
if (error) { Debug.message("読み込み失敗: " + message); return; }
if (target !== null && target isvalid) target.copyFromBitmapToMainImage(this);
}
}
var img = new AsyncImage(layer);
img.loadAsync("bg.png"); // 完了は onLoaded に通知される
Warning
While asynchronous loading is in progress, accessing any member other than Bitmap.loading raises an exception.
Also, the target Layer may already be invalidated by the time onLoaded is called, so check its validity as in the example above.
Process a Layer's image and write it back¶
var tmp = new Bitmap();
layer.copyToBitmapFromMainImage(tmp); // 共有されるだけなので速い
ImageFunction.doGrayScale(tmp); // ここで初めて実体が分離される
ImageFunction.doBoxBlur(tmp, 4, 4);
layer.copyFromBitmapToMainImage(tmp); // 書き戻し (update が走る)
See also¶
- Layer class reference
- Bitmap class reference
- ImageFunction class reference
- BitmapLayerTreeOwner class reference
- Graphic System — meaning of layer types, drawing faces (
df*), and operation modes (om*)