利用者:Kwk/Gsoc2010/ImageLayers

提供: wiki
< 利用者:Kwk‎ | Gsoc2010
2010年8月17日 (火) 01:48時点におけるwiki>Kwkによる版 (End-User Documentation: Video demo)
(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)
移動先: 案内検索

About

This page is about a task in my GSoC 2010 project.

Scenario

Consider you want to edit (e.g. paint onto) images that have multiple layers stored inside them. Maybe you want to add or remove a layer or move the layers around to change how they are blended/mixed/...

Examples of image editing software that can handle such images are The GIMP or Photoshop for example.

Problem

Currently blender has not built in support for multi-layered images in the sense of images that lie on top of each other. There is no way to display layered images at once (e.g. by blending/mixing them) nor to edit them.

Blender and OpenEXR

If you want to take a look how Blender handles multi-layered images you can go to the render menu, set the output to OpenEXR and add a couple of render layers each with individual passes. Then press the "Image" button to render for instance the default cube.

A UV/Image Editor will pop up that shows one image at a time even though the OpenEXR image consists of multiple images. Therefore Blender has a menu to select the "Render Layer" and the "Pass" in this render.

It is OK to use OpenEXR like that because it doesn't force you to use it's multi-layer capabilities in any specific way. In the OpenEXR way of thinking all the images live side by side. And OpenEXR doesn't know about Render layers or passes at all. This is something that Blender does by naming the images that are created during the rendering in a way so it can extract that information later.

Blender internally can store 8 render layers per Image which is not much. Each render layer itself can store unlimited amounts of render passes.

When you save a rendered image using the "MultiLayer" (NOT "OpenEXR") format and open it using the OpenEXR inspection tool "exrheader", you can see how Blender names the render layers and passes.

mine:~ konrad$ exrheader Desktop/multilayer.exr 

Desktop/multilayer.exr:

file format version: 2, flags 0x0
BlenderMultiChannel (type string): "Blender V2.43 and newer"
channels (type chlist):
    RenderLayer.001.AO.B, 32-bit floating-point, sampling 1 1
    RenderLayer.001.AO.G, 32-bit floating-point, sampling 1 1
    RenderLayer.001.AO.R, 32-bit floating-point, sampling 1 1
    RenderLayer.001.Combined.A, 32-bit floating-point, sampling 1 1
    RenderLayer.001.Combined.B, 32-bit floating-point, sampling 1 1
    RenderLayer.001.Combined.G, 32-bit floating-point, sampling 1 1
    RenderLayer.001.Combined.R, 32-bit floating-point, sampling 1 1
    RenderLayer.001.Depth.Z, 32-bit floating-point, sampling 1 1
    RenderLayer.001.Env.B, 32-bit floating-point, sampling 1 1
    RenderLayer.001.Env.G, 32-bit floating-point, sampling 1 1
    RenderLayer.001.Env.R, 32-bit floating-point, sampling 1 1
    RenderLayer.001.Indirect.B, 32-bit floating-point, sampling 1 1
    RenderLayer.001.Indirect.G, 32-bit floating-point, sampling 1 1
    RenderLayer.001.Indirect.R, 32-bit floating-point, sampling 1 1
    RenderLayer.001.Reflect.B, 32-bit floating-point, sampling 1 1
    RenderLayer.001.Reflect.G, 32-bit floating-point, sampling 1 1
    RenderLayer.001.Reflect.R, 32-bit floating-point, sampling 1 1
    RenderLayer.001.Refract.B, 32-bit floating-point, sampling 1 1
    RenderLayer.001.Refract.G, 32-bit floating-point, sampling 1 1
    RenderLayer.001.Refract.R, 32-bit floating-point, sampling 1 1
    RenderLayer.001.UV.A, 32-bit floating-point, sampling 1 1
    RenderLayer.001.UV.U, 32-bit floating-point, sampling 1 1
    RenderLayer.001.UV.V, 32-bit floating-point, sampling 1 1
    RenderLayer.Combined.A, 32-bit floating-point, sampling 1 1
    RenderLayer.Combined.B, 32-bit floating-point, sampling 1 1
    RenderLayer.Combined.G, 32-bit floating-point, sampling 1 1
    RenderLayer.Combined.R, 32-bit floating-point, sampling 1 1
    RenderLayer.Depth.Z, 32-bit floating-point, sampling 1 1
    RenderLayer.Diffuse.B, 32-bit floating-point, sampling 1 1
    RenderLayer.Diffuse.G, 32-bit floating-point, sampling 1 1
    RenderLayer.Diffuse.R, 32-bit floating-point, sampling 1 1
    RenderLayer.Emit.B, 32-bit floating-point, sampling 1 1
    RenderLayer.Emit.G, 32-bit floating-point, sampling 1 1
    RenderLayer.Emit.R, 32-bit floating-point, sampling 1 1
    RenderLayer.Shadow.B, 32-bit floating-point, sampling 1 1
    RenderLayer.Shadow.G, 32-bit floating-point, sampling 1 1
    RenderLayer.Shadow.R, 32-bit floating-point, sampling 1 1
    RenderLayer.Spec.B, 32-bit floating-point, sampling 1 1
    RenderLayer.Spec.G, 32-bit floating-point, sampling 1 1
    RenderLayer.Spec.R, 32-bit floating-point, sampling 1 1
compression (type compression): zip, multi-scanline blocks
dataWindow (type box2i): (0 0) - (959 539)
displayWindow (type box2i): (0 0) - (959 539)
lineOrder (type lineOrder): increasing y
pixelAspectRatio (type float): 1
screenWindowCenter (type v2f): (0 0)
screenWindowWidth (type float): 1

As you can see Blender separates Render Layers and Passes by a dot ("."). To OpenEXR, there is no distinction between render layers, passes or whatsoever. It simply stores each so called "channel" side by side. One thing you should also notice is that OpenEXR can store channels with different color channels in one single file. This is especially great if you want to have for instance your diffuse (RGBA) and bump map (Grayscale) in one file.

Idea

The idea of this task is to add some basic functionality for handling multi-layered images in Blender. There shall be for instance a list box showing all the layers inside an image. The user shall be able to re-arrange the order of layers, add or remove layers and adjust properties of each layer.

End-User Documentation

You can take any image in Blender and add layers to it. By default there's one layer present in any image. It is the image itself as you would see it when not using image layers.

Layers in Blender
There are many layers in Blender, for instance the render layers, the scene layers, the armature layers and sculpt layers. Image layers can be considered as pictures, stacked on top of each other.


Layers in other painting packages
If you're not familiar with image layers from other painting packages like The GIMP [1] or Photoshop, I suggest to start reading about what layers are in those packages. It's not too hard to understand though.


This picture shows the UI for image layers:

The UI for image layers

You can find this UI in the UV/Image Editor in the properties panel that can be made visible with View » Properties.

The UI shows a bunch of image layers in a list. Each layer can be moved up and down with the little arrow icons. You can add more layers or remove one with the little "+" and "-" buttons. Each new layer will have the same dimensions and color channels as the original image.

You can have as many layers as you want (or can handle).

Layer Properties

Each layer has a name, an opacity and an invisibility flag.

Name

The name can be chosen freely and there's no need for a layer to be unique. Every new layer will be given a name like "ImageLayer.XXX", where XXX stands for an ongoing number.

Opacity

The opacity of a layer determines the extent to which it lets colors from layers above, it in the stack show through. Opacity ranges from 0 to 1, with 0 meaning complete transparency, and 1 meaning complete opacity! (This text is inspired by The GIMP).

Visibility

When this property is unchecked, the selected layer will be hidden from the UV/Image Editor. You can toggle a layer's visibility by checking or unchecking this box.

The Layer Stack
Unlike other painting programs, the layers are rendered from top to bottom. This order can be changed later if it's requested by users.


Painting on Layers

There is always one active layer at a time. It is highlighted with "blue" as can be seen in the above screen shot. You can use the normal paint tools to draw on the active layer.

Painting only on base layer
Currently the painting is only working on the Base Layer. I have a pretty ugly hack to fix this, but it's not final.


Video demonstration

Here's a video [2] demonstration of some of the mulit-layered image painting features.

Technical Details

I've talked to Bob and Tom and we agreed to start out with some basic implementation that does no harm to existing code. I've also talked to Nicholas Bishop and we agreed that the first thing one should do is to add the datastructures to the code and to show the list box I've talked about earlier in the UI.

Implementation

The implementation details you see below is far from complete but should give a impression.

Incomplete and outdated
The code below is only here to give the impression how I designed multi-layered images. The code is probably outdated by the time you read this. The code is available as a patch for trunk in my branch directory (soc-2010-kwk/patches).


Loading and Saving
Up until now, I haven't implemented loading and saving of image layers together with the image.


Data structures

At first I want the image layers to break nothing in Blender. Therfore I've added a list to the end of the Image struct (in "DNA_image_types.h"):

typedef struct Image {
	/*...*/
	ListBase imlayers;
} Image;

This way, each individual Image can have multiple Image Layers.

As you can see, the image layers live completely aside from the rest of the image. Although I am thinking about sharing properties from the "Image" struct like the size for instance with the "ImageLayers" struct.

I also need to modify the cleanup/free methods for an Image to clean all the layered image as well.

The ImageLayer struct

Here's how the image layer looks like for now (in "DNA_image_types.h"):

/*...*/

typedef struct ImageLayer {
	struct ImageLayer *next, *prev;
	char name[IMA_LAYER_MAX_LEN];
	float opacity;
	int flag;
	ListBase ibufs; /* Stores only one ImBuf */
} ImageLayer;

/*...*/

The flag attribute can store a bit flag combination of these bits:

  • IMA_LAYER_CURRENT: If is set, the image layer is selected in the UI
  • IMA_LAYER_INVISIBLE: If set, the image layer is invisible in the UV/Image Editor
  • IMA_LAYER_BASE: This is a special flag! If set, the stored ibufs are not to be deleted because it is only a link to the Image->ibufs.first image buffer. This way you can have an image layer that contains what is normally displayed in the UV/Image Editor.

RNA for ImageLayer

The RNA definition of the image struct looks like this in "rna_image.c":

/*...*/

static PointerRNA rna_Image_active_image_layer_get(PointerRNA *ptr)
{
	Image *ima= (Image*)ptr->data;
	ImageLayer *layer= imalayer_get_current(ima);

	return rna_pointer_inherit_refine(ptr, &RNA_ImageLayer, layer);
}

static int rna_Image_active_image_layer_index_get(PointerRNA *ptr)
{
	Image *ima= (Image*)ptr->data;
	return imalayer_get_current_num(ima);
}

static void rna_Image_active_image_layer_index_set(PointerRNA *ptr, int value)
{
	Image *ima= (Image*)ptr->data;
	imalayer_set_current_num(ima, value);
}

static void rna_Image_active_image_layer_index_range(PointerRNA *ptr, int *min, int *max)
{
	Image *im= (Image*)ptr->data;

	*min= 0;
	*max= BLI_countlist(&im->imlayers)-1;
	*max= MAX2(0, *max);
}

/*...*/


static void rna_def_image_layer(BlenderRNA *brna)
{
		StructRNA *srna;
		PropertyRNA *prop;
		FunctionRNA *func;

		srna= RNA_def_struct(brna, "ImageLayer", NULL);
		RNA_def_struct_ui_text(srna, "Image Layer", "Image layer");

		prop= RNA_def_property(srna, "name", PROP_STRING, PROP_NONE);
		RNA_def_property_string_sdna(prop, NULL, "name");
		RNA_def_property_string_default(prop, "ImageLayer");
		RNA_def_property_string_maxlength(prop, IMA_LAYER_MAX_LEN);
		RNA_def_property_update(prop, NC_OBJECT|ND_MODIFIER|NA_RENAME, NULL);
		RNA_def_property_ui_text(prop, "Name", "The name of the image layer.");
		RNA_def_struct_name_property(srna, prop);

		prop= RNA_def_property(srna, "opacity", PROP_FLOAT, PROP_NONE);
		RNA_def_property_float_sdna(prop, NULL, "opacity");
		RNA_def_property_float_default(prop, 1.0f);
		RNA_def_property_range(prop, 0.0, 1.0);
		RNA_def_property_ui_text(prop, "Opacity", "The opacity of the image layer when blended.");
		RNA_def_property_update(prop, NC_IMAGE|ND_DISPLAY, NULL);

		prop= RNA_def_property(srna, "invisible", PROP_BOOLEAN, PROP_NONE);
		RNA_def_property_boolean_sdna(prop, NULL, "flag", IMA_LAYER_INVISIBLE);
		RNA_def_property_ui_text(prop, "Invisible", "Hides the layer in the UV/Image Editor");
		RNA_def_property_update(prop, NC_IMAGE|ND_DISPLAY, NULL);
}

/* ... */

static void rna_def_image(BlenderRNA *brna)
{
	/*...*/
	prop= RNA_def_property(srna, "field_order", PROP_ENUM, PROP_NONE);
	RNA_def_property_enum_bitflag_sdna(prop, NULL, "flag");
	RNA_def_property_enum_items(prop, prop_field_order_items);
	RNA_def_property_ui_text(prop, "Field Order", "Order of video fields. Select which lines are displayed first");
	RNA_def_property_update(prop, NC_IMAGE|ND_DISPLAY, NULL);
	
	/* Image Layers */

	/* Image Layers */

	prop= RNA_def_property(srna, "image_layers", PROP_COLLECTION, PROP_NONE);
	RNA_def_property_collection_sdna(prop, NULL, "imlayers", NULL);
	//RNA_def_property_collection_funcs(prop, "rna_Image_layers_begin", "rna_iterator_listbase_next", "rna_iterator_listbase_end", "rna_iterator_listbase_get", 0, 0, 0);
	RNA_def_property_struct_type(prop, "ImageLayer");
	RNA_def_property_ui_text(prop, "Image Layers", "");

	prop= RNA_def_property(srna, "active_image_layer", PROP_POINTER, PROP_NONE);
	RNA_def_property_struct_type(prop, "ImageLayer");
	RNA_def_property_pointer_funcs(prop, "rna_Image_active_image_layer_get", NULL, NULL, NULL);
	RNA_def_property_ui_text(prop, "Active Image Layer", "Active image layer");
	RNA_def_property_update(prop, NC_IMAGE|ND_DISPLAY, NULL);

	prop= RNA_def_property(srna, "active_image_layer_index", PROP_INT, PROP_UNSIGNED);
	RNA_def_property_int_funcs(prop, "rna_Image_active_image_layer_index_get", "rna_Image_active_image_layer_index_set", "rna_Image_active_image_layer_index_range");
	RNA_def_property_ui_text(prop, "Active Image Layer Index", "Index of active image layer slot");
	RNA_def_property_update(prop, NC_IMAGE|ND_DISPLAY, NULL);

	/*...*/
}

void RNA_def_image(BlenderRNA *brna)
{
	rna_def_image_layer(brna);
	rna_def_image(brna);
	rna_def_imageuser(brna);
}

/*...*/

Operators for ImageLayer

There are operators to:

  • Add a layer
  • Remove a layer
  • Move a layer up and down
  • Fill a layer with a constant color (broken)
/********************** image layer operators *********************/

int image_layer_poll(bContext *C)
{
	return NULL != CTX_data_edit_image(C);
}

static int image_layer_add_exec(bContext *C, wmOperator *op)
{
	Image *im= CTX_data_edit_image(C);

	if(!im)
		return OPERATOR_CANCELLED;

	image_add_image_layer(im);
	WM_event_add_notifier(C, NC_IMAGE|ND_DRAW, NULL);

	return OPERATOR_FINISHED;
}

void IMAGE_OT_image_layer_add(wmOperatorType *ot)
{
	/* identifiers */
	ot->name= "Add Image Layer";
	ot->idname= "IMAGE_OT_image_layer_add";
	ot->description="Add a new image layer";

	/* api callbacks */
	ot->exec= image_layer_add_exec;
	ot->poll = image_layer_poll;

	/* flags */
	ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
}

static int image_layer_remove_exec(bContext *C, wmOperator *op)
{
	Image *ima= CTX_data_edit_image(C);

	if(!ima)
		return OPERATOR_CANCELLED;

	image_remove_current_image_layer(ima);
	WM_event_add_notifier(C, NC_IMAGE|ND_DRAW, NULL);

	return OPERATOR_FINISHED;
}

void IMAGE_OT_image_layer_remove(wmOperatorType *ot)
{
	/* identifiers */
	ot->name= "Remove Image Layer";
	ot->idname= "IMAGE_OT_image_layer_remove";
	ot->description="Remove the selected image layer";

	/* api callbacks */
	ot->exec= image_layer_remove_exec;
	ot->poll = image_layer_poll;

	/* flags */
	ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
}

static int image_layer_fill_color_exec(bContext *C, wmOperator *op)
{
	float color[4];

	Image *ima= CTX_data_edit_image(C);

	if(!ima)
		return OPERATOR_CANCELLED;

	RNA_float_get_array(op->ptr, "color", color);

	imalayer_fill_color(ima, color);

	WM_event_add_notifier(C, NC_IMAGE|ND_DRAW, NULL);

	return OPERATOR_FINISHED;
}

void IMAGE_OT_image_layer_fill_color(wmOperatorType *ot)
{
	const float defcol[4] = {0.0f, 0.0f, 0.0f, 0.0f};

	/* identifiers */
	ot->name= "Fill Image Layer with Color";
	ot->idname= "IMAGE_OT_image_layer_fill_color";
	ot->description="Fill the image layer with a constant color";

	/* api callbacks */
	ot->exec= image_layer_fill_color_exec;
	ot->poll = image_layer_poll;
	ot->invoke= WM_operator_props_popup;

	/* flags */
	ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;

	/* properties */
	RNA_def_float_color(ot->srna, "color", 4, defcol, 0.0f, 1.0f, "Fill Color", "Color used to fill the layer", 0.0f, 1.0f);
}

static int image_layer_move_exec(bContext *C, wmOperator *op)
{
	Image *ima= CTX_data_edit_image(C);
	ImageLayer *layer, *tmp;
	int type, layerID;

	if(!ima)
		return OPERATOR_CANCELLED;

	layer= imalayer_get_current(ima);

	if (!layer)
		return OPERATOR_CANCELLED;

	type= RNA_enum_get(op->ptr, "type");
	layerID= imalayer_get_current_num(ima);

	if (type == -1) { /* Move direction: Up */
		tmp= layer->prev;
		BLI_remlink(&ima->imlayers, layer);
		layer->next = layer->prev = NULL;
		if (tmp) {
			BLI_insertlinkbefore(&ima->imlayers, tmp, layer);
		}
		else {
			BLI_addhead(&ima->imlayers, layer);
		}
	}
	else { /* Move direction: Down */
		tmp= layer->next;
		BLI_remlink(&ima->imlayers, layer);
		layer->next = layer->prev = NULL;
		if (tmp) {
			BLI_insertlinkafter(&ima->imlayers, tmp, layer);
		}
		else {
			BLI_addtail(&ima->imlayers, layer);
		}
	}

	WM_event_add_notifier(C, NC_IMAGE|ND_DRAW, NULL);

	return OPERATOR_FINISHED;
}

void IMAGE_OT_image_layer_move(wmOperatorType *ot)
{
	static EnumPropertyItem slot_move[] = {
		{-1, "UP", 0, "Up", ""},
		{1, "DOWN", 0, "Down", ""},
		{0, NULL, 0, NULL, NULL}
	};

	/* identifiers */
	ot->name= "Move Imahe Layer";
	ot->idname= "IMAGE_OT_image_layer_move";
	ot->description="Move image layers up and down";

	/* api callbacks */
	ot->exec= image_layer_move_exec;

	/* flags */
	ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;

	/* properties */
	RNA_def_enum(ot->srna, "type", slot_move, 0, "Type", "");
}

Helper functions

These are the prototypes for the helper functions followed by their implementation:

/* Removes all image layers from the image "ima" */
void image_free_image_layers(struct Image *ima);

/* Frees an image layer and associated memory */
void free_image_layer(struct ImageLayer *layer);

/* Removes the currently selected image layer */
int image_remove_current_image_layer(struct Image *ima);

/* Adds another image layer and selects it */
int image_add_image_layer(struct Image *ima);

/* Adds the base layer of images that points Image->ibufs.first */
int image_add_image_layer_base(struct Image *ima);

/* Returns the index of the currently selected image layer */
short imalayer_get_current_num(struct Image *ima);

/* Selects the image layer with the number specified in "value" */
void imalayer_set_current_num(struct Image *ima, short value);

/* Returns the image layer that is currently selected */
struct ImageLayer *imalayer_get_current(struct Image *ima);

/* Fills the current selected image layer with the color given */
void imalayer_fill_color(struct Image *ima, float color[4]);

/* image layers */

void image_free_image_layers(struct Image *ima)
{
	ImageLayer *layer, *next;
	ImBuf *ibuf;
	void *lock;

	if(ima==0)
		return;

	ibuf= BKE_image_acquire_ibuf(ima, NULL, &lock);

	layer=(ImageLayer*)(ima->imlayers.first);
	while(layer) {
		next= layer->next;
		BLI_remlink(&ima->imlayers, layer);
		free_image_layer(layer);
		layer= next;
	}

	BLI_freelistN(&ima->imlayers);

	BKE_image_release_ibuf(ima, lock);
}

void free_image_layer(ImageLayer *layer)
{
	ImBuf *ibuf, *next;

	if (!layer)
		return;

	ibuf= (layer->ibufs).first;
	while(ibuf) {
		next= ibuf->next;
		BLI_remlink(&layer->ibufs, ibuf);

		if((layer->flag & IMA_LAYER_BASE)==0) {
			if (ibuf->userdata) {
				MEM_freeN(ibuf->userdata);
				ibuf->userdata = NULL;
			}

			IMB_freeImBuf(ibuf);
		}
		ibuf= next;
	}
	/*BLI_freelistN(&layer->ibufs);*/

	MEM_freeN(layer);
}

ImageLayer *imalayer_get_current(Image *ima)
{
	ImageLayer *layer;
	if(ima==0)
		return 0;

	for(layer=ima->imlayers.first; layer; layer=layer->next){
		if(layer->flag & IMA_LAYER_CURRENT)
			return layer;
	}

	return 0;
}

short imalayer_get_current_num(Image *ima)
{
	ImageLayer *layer;
	short i;

	if(ima==0)
		return 0;

	for(layer=ima->imlayers.first, i=0; layer; layer=layer->next, i++)
		if(layer->flag & IMA_LAYER_CURRENT)
			return i;

	return i;
}

void imalayer_set_current_num(Image *ima, short index)
{
	ImageLayer *layer;
	short i;

	if(ima==0)
		return;

	for(layer=ima->imlayers.first, i=0; layer; layer=layer->next, i++) {
		if(i == index)
			layer->flag |= IMA_LAYER_CURRENT;
		else
			layer->flag &= ~IMA_LAYER_CURRENT;
	}
}

void imalayer_fill_color(struct Image *ima, float color[4])
{
	ImageLayer *layer= NULL;
	ImBuf *ibuf= NULL;
	unsigned char *rect= NULL;
	float *rect_float= NULL;
	void *lock;

	if (ima==NULL)
		return FALSE;

	ibuf= BKE_image_acquire_ibuf(ima, NULL, &lock);


	layer= imalayer_get_current(ima);

	if ((ibuf = ima->imlayers.first)) {
		if (ibuf->flags & IB_rectfloat) {
			rect_float= (float*)ibuf->rect_float;
		}
		else {
			rect= (unsigned char*)ibuf->rect;
		}

		//BKE_image_buf_fill_color(rect, rect_float, ibuf->x, ibuf->y, color);
	}

	BKE_image_release_ibuf(ima, lock);
}

int image_remove_current_image_layer(Image *ima)
{
	ImageLayer *layer= NULL;

	if(ima==NULL)
		return FALSE;

	layer= imalayer_get_current(ima);

	free_image_layer(layer);

	/* Ensure the first element in list gets selected (if any) */
	if(ima->imlayers.first)
		((ImageLayer *) ima->imlayers.first)->flag |= IMA_LAYER_CURRENT;

	return TRUE;
}

/* Works in most of the cases but since no unique name is nessecary, this is acceptable as a start. */
static void imalayer_unique_name(const ListBase *imlayers, ImageLayer *newlayer)
{
	char *new_name = 0;
	int new_len = 0;
	int suffix;
	ImageLayer *i;
	char *name = newlayer->name;

	/* When list is empty, there's no need to check name */
	if (!imlayers->first)
		return;

	/* Iterate over every list element and check */
	for(i=imlayers->first; i; i=i->next) {

		/* If names differ, go to next element */
		if(strcmp(name, i->name))
			continue;

		if(!new_name) {
			int len = strlen(name);

			if(len >= 4 && sscanf(name + len - 4, ".%03d", &suffix) == 1) {
				new_len = len;
			}
			else {
				suffix = 0;
				new_len = len + 4;
				if(new_len > 31)
					new_len = 31;
			}

			new_name = MEM_mallocN(new_len + 1, "new_name");
			strcpy(new_name, name);
			name = new_name;
		}
		sprintf(new_name + new_len - 4, ".%03d", ++suffix);
	}

	if(new_name) {
		strcpy(newlayer->name, new_name);
		MEM_freeN(new_name);
	}
}

int image_add_image_layer(Image *ima)
{
	ImageLayer *layer= NULL;
	ImBuf *ibuf, *imaibuf;
	float color[4] = {0.0f, 1.0f, 0.0f, 1.0f};
	void *lock;

	if(ima==NULL)
		return FALSE;

	ibuf= BKE_image_acquire_ibuf(ima, NULL, &lock);

	/* Deselect other layers */
	layer = ima->imlayers.first;
	for(; layer; layer=layer->next)
		layer->flag &= ~IMA_LAYER_CURRENT;

	layer= (ImageLayer*) MEM_callocN(sizeof(ImageLayer), "image_layer");
	layer->next = layer->prev = NULL;

	/*if(BLI_countlist(&ima->imlayers)>0)
		sprintf(layer->name, "ImageLayer %i", BLI_countlist(&ima->imlayers));
	else
		strcpy(layer->name, "ImageLayer");*/

	strcpy(layer->name, "ImageLayer");
	imalayer_unique_name(&ima->imlayers, layer);

	layer->opacity = 1.0f;
	layer->flag = IMA_LAYER_CURRENT;

	/* Get ImBuf from ima */
	imaibuf = (ImBuf*)ima->ibufs.first;
	ibuf = add_ibuf_size(imaibuf->x, imaibuf->y, layer->name, imaibuf->depth, imaibuf->flags, 0, color);
	BLI_addtail(&layer->ibufs, ibuf);
	BLI_addtail(&ima->imlayers, layer);

	BKE_image_release_ibuf(ima, lock);

	return TRUE;
}

/* TODO: (kwk) Base image layer needs proper locking... */
int image_add_image_layer_base(Image *ima)
{
	ImageLayer *layer= NULL;
	ImBuf *ibuf;
	void *lock;

	if(ima==NULL)
		return FALSE;

	ibuf= BKE_image_acquire_ibuf(ima, NULL, &lock);

	/* Deselect other layers */
	layer = ima->imlayers.first;
	for(; layer; layer=layer->next)
		layer->flag &= ~IMA_LAYER_CURRENT;

	layer= (ImageLayer*) MEM_callocN(sizeof(ImageLayer), "image_layer");
	layer->next = layer->prev = NULL;

	strcpy(layer->name, "BaseLayer");
	imalayer_unique_name(&ima->imlayers, layer);

	layer->opacity = 1.0f;
	layer->flag = IMA_LAYER_CURRENT | IMA_LAYER_BASE; /* BASE causes no free on deletion of layer */

	/* Get ImBuf from ima */
	ibuf = (ImBuf*)ima->ibufs.first;
	BLI_addtail(&layer->ibufs, ibuf);
	BLI_addtail(&ima->imlayers, layer);

	BKE_image_release_ibuf(ima, lock);

	return TRUE;
}

Basic UI setup

For the UI to display a basic list box I've added this Python code:

class IMAGE_PT_image_layers(bpy.types.Panel):
    bl_space_type = 'IMAGE_EDITOR'
    bl_region_type = 'UI'
    bl_label = "Image Layers"

    def poll(self, context):
        sima = context.space_data
        return (sima and (sima.image))

    def draw(self, context):
        layout = self.layout

        sima = context.space_data
        ima = sima.image
        show_uvedit = sima.show_uvedit
        uvedit = sima.uv_editor
        wide_ui = context.region.width > narrowui
        layers = ima.image_layers

        split = layout.split()
        
        col = split.column()
        
        if ima:            
            row = layout.row()

            row.template_list(ima, "image_layers", ima, "active_image_layer_index", rows=4)

            col = row.column(align=True)
            col.operator("image.image_layer_add", text="", icon='ZOOMIN')

            if ima.active_image_layer:
                col.operator("image.image_layer_remove", text="", icon='ZOOMOUT')
                
                col.operator("image.image_layer_move", text="", icon='TRIA_UP').type = 'UP'
                col.operator("image.image_layer_move", text="", icon='TRIA_DOWN').type = 'DOWN'

                row = layout.row()
                
                row.operator("image.image_layer_fill_color", text="Fill with Color")

                row = layout.row()
                
                row.label(text="Name")
                col = row.column(align=True)
                col.prop(ima.active_image_layer, "name", text="")
    
                row = layout.row()
                
                row.label(text="Opacity:")
                col = row.column(align=True)
                col.prop(ima.active_image_layer, "opacity", text="")
                
                row = layout.row()
                
                row.label(text="Invisible:")
                col = row.column(align=True)
                col.prop(ima.active_image_layer, "invisible", text="")