View Full Version : Gimp style auto levels - anyone want it?
4evrplan
4th September 2009, 22:42
In spite of not knowing what I'm doing, I've managed to hack together a simple plugin from bits of source code I found on the web that does color auto levels the same way The GIMP does (with a slight variation that garuantees optimal frames aren't changed). My questions are 1) would anyone else even want this 2) if so, what's the proper way to distribute it, and would it even be legal to do so? I'm not opposed to RTM, so to speak, I just don't know where to look, so if there's a guide to this sort of stuff, just give me a link, and I'll be happily on my way.
A note about the plugin: It only works with RGB32 images, and if someone wants to use it, be sure to crop out any letter boxing first so the darker border doesn't mess up the correction. Also, it doesn't account for neighboring frames, so it's possible (ahem, likely) to have flicker where some frames are adjusted much more than others. This is just because I don't know how to do the temporal stuff yet. The plugin doesn't do any kind of error trapping, just assumes everything's dandy and chugs along merrily (again, because I don't know how yet), so be sure your source is RGB32.
EDIT: See post #6 for the code. The color space error trapping is now working.
Fizick
5th September 2009, 06:55
2) probably original source code have some license comment in head or in readme. If it was GPL, your plugin must be GPL too. Distribute modified source code with them.
Where is the web link?
SEt
5th September 2009, 12:25
If you used others' code to understand the algorithm, but written all your code yourself you free to use any license you want. There are issues with patents but i don't think it applies here.
Fizick
5th September 2009, 21:44
SEt,
if you write yourself exactly same code (big fragments), probably you can not use arbitrary license ( i remember rewriting of modified yadif as SBdeint ;) )
SEt
6th September 2009, 14:29
Variation of how you can efficiently implement simple things is limited. As example yadif algorithm isn't complex and SBdeint implements very similar one so the difference between main asm code is probably as big as possible for both to be efficient. (C code as algorithm explanation was deliberately written like yadif so what is the same and what is different would be easy to understand.)
Side note for why i don't develop or mention SBdeint: I hope that it and yadif both "disappear from public" so newbies won't accidentally misuse them (as part of "make it look great with just one click/function" program/script). That approach produces unpleasantly synthetic image, so should be only used where realtime processing is a must, not for encoding where we have things like nnedi and eedi2. It's a pity to see home video encoded after yadif as even encoded without any deinterlacing to xvid and then processed usually looks better.
4evrplan
8th September 2009, 17:15
Okay, here it is. I'm releasing this GPL. Please let me know what you think. Be brutal (but constructive), and I'll work to make it better. Also, how are some of you putting code in a little separate scrolling box (EDIT: code now in a box)?
#include <windows.h>
#include <math.h>
#include "avisynth.h"
class GimpAutoLevels : public GenericVideoFilter {
public:
GimpAutoLevels(PClip _child, IScriptEnvironment* env) : GenericVideoFilter(_child) {
if (!vi.IsRGB32()) {
env->ThrowError("GimpAutoLevels: Supports RGB32 color format only");
}
}
PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* env);
};
PVideoFrame __stdcall GimpAutoLevels::GetFrame(int n, IScriptEnvironment* env) {
PVideoFrame srcFrame = child->GetFrame(n, env);
PVideoFrame dstFrame = env->NewVideoFrame(vi);
const unsigned char* src = srcFrame->GetReadPtr();
unsigned char* psrc = (unsigned char*)src;
unsigned char* dst = dstFrame->GetWritePtr();
unsigned char* pdst = dst;
const int row_size = dstFrame->GetRowSize();
const int src_pad = srcFrame->GetPitch() - srcFrame->GetRowSize();
const int dst_pad = dstFrame->GetPitch() - row_size;
const int height = dstFrame->GetHeight();
const int width = row_size / 4;
unsigned long hist[3][256];
unsigned char low, high;
int i, c;
double mult, count, percentage, next_percentage;
// Create a histogram; firt initialize it to 0.
for (c = 0; c < 3; c++) {
for (i = 0; i < 256; i++) {
hist[c][i] = 0;
}
}
// Fill the histogram by counting the number of pixels with each value
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
hist[0][psrc[0]]++;
hist[1][psrc[1]]++;
hist[2][psrc[2]]++;
pdst += 4;
psrc += 4;
}
pdst += dst_pad;
psrc += src_pad;
}
count = width * height;
// Fore each channel
for (c = 0; c < 3; c++) {
// Determine the low input value
next_percentage = hist[c][0] / count;
for (i = 0; i < 255; i++) {
percentage = next_percentage;
next_percentage += hist[c][i+1] / count;
if (fabs(percentage - 0.006) < fabs(next_percentage - 0.006)) {
low = i;//i+1; This is a deviation from the way The GIMP does it
// that prevents any change in the image if it's
// already optimal
break;
}
}
// Determine the high input value
next_percentage = hist[c][255] / count;
for (i = 255; i > 0; i--) {
percentage = next_percentage;
next_percentage += hist[c][i-1] / count;
if (fabs(percentage - 0.006) < fabs(next_percentage - 0.006)) {
high = i;//i-1; This is a deviation from the way The GIMP does it
// that prevents any change in the image if it's
// already optimal
break;
}
}
// Turn the histogram into a look up table to stretch the values
mult = 255.0L / (high - low);
for (i = 0; i < low; i++) {
hist[c][i] = 0;
}
for (i = 255; i > high; i--) {
hist[c][i] = 255;
}
double base = 0.0L;
for (i = low; i <= high; i++) {
hist[c][i] = (unsigned long)base;
base += mult;
}
}
// Now apply the changes (stretch the values)
psrc = (unsigned char*)src;
pdst = dst;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
pdst[0] = (unsigned char)hist[0][psrc[0]];
pdst[1] = (unsigned char)hist[1][psrc[1]];
pdst[2] = (unsigned char)hist[2][psrc[2]];
pdst += 4;
psrc += 4;
}
pdst += dst_pad;
psrc += src_pad;
}
return dstFrame;
}
AVSValue __cdecl Create_GimpAutoLevels(AVSValue args, void* user_data, IScriptEnvironment* env) {
return new GimpAutoLevels(args[0].AsClip(), env);
}
extern "C" __declspec(dllexport) const char* __stdcall AvisynthPluginInit2(IScriptEnvironment* env) {
env->AddFunction("GimpAutoLevels", "c", Create_GimpAutoLevels, 0);
return "`GimpAutoLevels' sample plugin";
}
Gavino
8th September 2009, 17:54
how are some of you putting code in a little separate scrolling box?
Enclose it in code and /code tags.
Looking at the code, I see it will only work with RGB32 input.
It would be better if it reported an error in other cases rather than producing garbage or possibly crashing.
4evrplan
8th September 2009, 17:59
Gavino, I couldn't agree more. When I was first writing it, IIRC, I tried env->ThrowError(), and tried it with a YUV clip, but I never saw the error message, just a blank screen. I'll try again soon.
Gavino
8th September 2009, 18:07
Did you put the check and throw the error in the constructor?
Doing it in GetFrame is too late.
4evrplan
8th September 2009, 18:11
When I add if (!vi.IsRGB32()) {
env->ThrowError("GimpAutoLevels only works with RGB32 clips");
}
, and I open the script in VDubMod, it doesn't throw the error unless I try to play the clip. If I simply click from one frame to the next, it happily shows nothing, but the frame count is correct. What gives?
EDIT: Answered above. Thanks Gavino.
4evrplan
8th September 2009, 20:01
The color space checking code is now working. Create_GimpAutoLevels() and the constructor changed.
vBulletin® v3.8.5, Copyright ©2000-2012, Jelsoft Enterprises Ltd.