Image Python API for Blender

A while back I started a patch for an image API in Blender, only basic operations are supported at the moment.

Update: committed the patch, while it’s only basic operations this makes it easier to accept improvements to the API.

Example use:

import imbuf

imb = imbuf.load("test.png")
imb.resize((30, 30))
imb.ppm = 33.0, 22.5
imbuf.write(imb, "test_out.png")
6 Likes

Either you are too fast, or I am too slow, in any case, well done !!!

1 Like

@kilon no matter - the API is very primitive - so there are many possible additions.

I assumed because you closed the task that you did not want for it to go further. I have to confess I am a bit confused about the whole Blender developer contribution process because I am new to it. But I am very interested into starting my own fork , I am to focus more on UI stuff but the image python api was one of my ideas so I dont have to use PIL and I can natively use Blender internal code. It would be better if things that are agreeable / acceptable from blender bf devs are contributed to blender source than to my own fork.

I was developing my own add on because I wanted to avoid modifying Blender source and maintaining my own fork but it became apparent that I was getting tired of the limitation of the Blender python api. Very powerful and well designed but not for what I wanted to do.

I am now in the process of decipher the blender source code (2.8 ui stuff mainly), I will take also a look at the image functions to see if I can make some small patches for contribution that will expose those c functions to python because I am definetly interested in extending the Blender Python API.

I will use this as a thread for my questions and ideas if you dont mind.

I’ve re-opened T54272 (corrected link) - since most operations here aren’t included in the initial commit.

Suggestions for basic patches for anyone wanting to get started:

  • copy method: (IMB_dupImBuf)
  • load image from memory (IMB_ibImageFromMemory).
  • load image from file handle (IMB_loadifffile).
  • create image from pixel buffer (IMB_allocFromBuffer), using bytes/bytearray - using Python’s buffer API.

We could also have byte/float operations added (convert between float/byte buffers), suggest to get byte access working well first.

Some image processing that I’ve done in Blender in the past has involved joining several images together, if that makes sense (imagine rendering an image in several ‘tiles’ and then wanting to join those tiles together into a single image).

This was quite difficult and I ended up just writing my own methods as I don’t think Blender’s pixels were in the right configuration for some of the image processing libraries (or maybe it was just my unfamiliarity with them). My methods were also quite slow. I’m not sure if imbuf would be ‘fast’ per se, but it would be definitely convenient.

One other thing would be some basic ‘mix/overlay’ functions, like being able to overlay an image with alpha over the top of another image. My example for when I’ve needed this is when rendering several images with different render borders and wanting to combine them. (It could be that being able to overlay images actually solves the need to ‘join’ images in my previous example.)

I’ve re-opened T55162 - since most operations here aren’t included in the initial commit.

@ideasman42 Did you reference the right development task here? Did you mean T54272?

Copying between images will be fast.

To begin with we could have copying (also called blitting) functions - see: IMB_BLEND_COPY use in IMB_rectblend. Id rather avoid supporting all blending options up front.

Ok I will start working on it on Monday. Starting with your suggestions. Will keep you posted.

@ideasman42 Is it ok if I work directly on Blender 2.8 code ?

I see you frequently merge master to 2.8 so getting the latest commits should not be an issue. Is there a reason to prefer the 2.79 [master] branch instead of the [blender 2.8] branch ?

[master] is delightfully stable compared to [blender 2.8], sure it doesn’t have all the shiny new features, but on the upside: it doesn’t have the instability of all those shiny new features either…

1 Like

@LazyDodo I am not afraid of the crashes, its part of the game anyway, but you make a valid point if I may not be able to use img_buf. I will give 2.8 a try and if all fails I will drop down to master. But if its a few crashes , I will stick with 2.8 cause I like to take advantage of the new code and it’s new features and I want to understand the new code base for my own fork which is why I decided to have a bite on this API as a warm up.

Ok I hope I ask this in the right place, after a day with battling with reducing my build times from 30 minutes to a minute, I managed to reduce it down to 10 seconds. Which makes coding bearable.

So first question I see the methods are formated like this example


static PyObject *M_imbuf_new(PyObject *UNUSED(self), PyObject *args, PyObject *kw)
{
	int size[2];
-->	static const char *_keywords[] = {"size", NULL};
	static _PyArg_Parser _parser = {"(ii)|i:new", _keywords, 0};
	if (!_PyArg_ParseTupleAndKeywordsFast(
	        args, kw, &_parser,
	        &size[0], &size[1]))
	{
		return NULL;
	}

	/* TODO, make options */
	uchar planes = 4;
	uint flags = IB_rect;

	ImBuf *ibuf = IMB_allocImBuf(UNPACK2(size), planes, flags);
	if (ibuf == NULL) {
		PyErr_Format(PyExc_ValueError, "new: Unable to create image (%d, %d)", UNPACK2(size));
		return NULL;
	}
	return Py_ImBuf_CreatePyObject(ibuf);
}

4th line, why the pointer variable is named _keywords[] instead of _args[] ?

Please note I am new to this Python C API dance, so I reading documentation while trying to understand Blender code using VS debugger. Things are starting to make sense but still I am in an infant mode.

Care to elaborate on this? Why did a build take 30 minutes for you? sure initial build will be a bit, but beyond that it should be both incremental compiles and links.

Probably I was doing something wrong and was rebuilding the whole thing.

In any case I am now using the lite release and 10 seconds are good enough for now and see in the future if I could reduce it down to 1. But right now my priority is to learn the Blender code and be useful. So that 1 second build time will have to wait.

I have developed a small library for C++ for live coding, which essentially build a super minimal executable and keep everything in DLLs so it keeps compile times in under a second and it does rebuids in the background while code is saved, it also keeps memory intact which is also handled by the DLLs. It was not my idea, I was inspired by another developer that publishes the handmade hero tutorials on youtube. His goal was to use C++ as a scripting language instead of python, like Blender does with its addons. Unreal uses also a similar technique for reloading/rebuilding C++ code, real time, while in its editor.

So build times is a temporary problem for now.

Of course Blender is far more monolithic so I will have to adjust that library, but as I said thats not what my priority is right now and for now I only care to understand the basics of the code , hence my question.

Allright them back to your question, it’s called keywords cause

  1. it contains the keywords
  2. it goes into the keywords field of the _PyArg_Parser structure.

but in all fairness, it’s just a variable name, could have been named LazyDodoLikesKittens and it would have still worked, however nicely chosen names do make the code more readable.

I’m not sure what the arguments could be for calling it _args.

1 Like

Well my logic was that since it refers to python arguments and not python keywords , I would expect it to be named _args but now I see as I suspected the rabbit hole goes deeper.

Yeah I am rather strict how I name things, but my motivation for this question was to understand the intention. To better understand how Blende deals with such functionality. I thought there was more it, glad to see the explanation is simple enough.

CPython’s convention is to use args for a tuple of ordered argumentrs, keywords or kw for a dict of named arguments.

@ideasman42 yes I know but that is what confused me

but what I did not know is that keyword arguments can be used as regular arguments as well in python taking their position into account so both version are the same

>>> i1 = imbuf.new([1024,768])
>>> i2 = imbuf.new(size=[1024,300])
>>> i1.size
(1024, 768)
>>> i2.size
(1024, 300)

I did not know python keywords had such behavior. So now the name makes sense and it is as it should be and good thing I asked cause as I suspected I was missing something and indeed I was

As you may suspect I tested it first using it as regular argument which is what confused me(see i1 in example).

Thanks mate.

Hooray!!!

My first patch, added support for image buffer duplication , get it now while it hot … just be careful so you wont be burned

More info here

https://developer.blender.org/D3480