I've upvoted Johannes' answer because he's right about that.
* A few comments have been raised that my original answer was not correct. It worked if alpha values were inverted from the normal. By definition, however, this won't work in most cases. I've therefore updated the formula below to be correct for the normal case. This ends up being equal to @hkurabko's answer below *
A more specific answer, however, incorporates the alpha value into the actual colour result based on an opaque background colour (or 'matte' as it's referred to).
There is an algorithm for this (from this wikipedia link):
- Normalise the RGBA values so that they're all between 0 and 1 - just divide each value by 255 to do this. We'll call the result
Source. - Normalise also the matte colour (black, white whatever). We'll call the result
BGColorNote - if the background colour is also transparent, then you'll have to recurse the process for that first (again, choosing a matte) to get the source RGB for this operation. Now, the conversion is defined as (in complete psuedo code here!):
Source => Target = (BGColor + Source) = Target.R = ((1 - Source.A) * BGColor.R) + (Source.A * Source.R) Target.G = ((1 - Source.A) * BGColor.G) + (Source.A * Source.G) Target.B = ((1 - Source.A) * BGColor.B) + (Source.A * Source.B)
To get the final 0-255 values for Target you simply multiply all the normalised values back up by 255, making sure you cap at 255 if any of the combined values exceed 1.0 (this is over-exposure and there are more complex algorithms dealing with this that involve whole-image processing etc.).
EDIT: In your question you said you want a white background - in that case just fix BGColor to 255,255,255.
Answer from Andras Zoltan on Stack OverflowVideos
How long does RGBA to RGB conversion take?
Will I lose quality converting RGBA to RGB?
Can I convert multiple RGBA files to RGB at once?
I've upvoted Johannes' answer because he's right about that.
* A few comments have been raised that my original answer was not correct. It worked if alpha values were inverted from the normal. By definition, however, this won't work in most cases. I've therefore updated the formula below to be correct for the normal case. This ends up being equal to @hkurabko's answer below *
A more specific answer, however, incorporates the alpha value into the actual colour result based on an opaque background colour (or 'matte' as it's referred to).
There is an algorithm for this (from this wikipedia link):
- Normalise the RGBA values so that they're all between 0 and 1 - just divide each value by 255 to do this. We'll call the result
Source. - Normalise also the matte colour (black, white whatever). We'll call the result
BGColorNote - if the background colour is also transparent, then you'll have to recurse the process for that first (again, choosing a matte) to get the source RGB for this operation. Now, the conversion is defined as (in complete psuedo code here!):
Source => Target = (BGColor + Source) = Target.R = ((1 - Source.A) * BGColor.R) + (Source.A * Source.R) Target.G = ((1 - Source.A) * BGColor.G) + (Source.A * Source.G) Target.B = ((1 - Source.A) * BGColor.B) + (Source.A * Source.B)
To get the final 0-255 values for Target you simply multiply all the normalised values back up by 255, making sure you cap at 255 if any of the combined values exceed 1.0 (this is over-exposure and there are more complex algorithms dealing with this that involve whole-image processing etc.).
EDIT: In your question you said you want a white background - in that case just fix BGColor to 255,255,255.
hm... regarding to
http://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
solution provided by Andras Zoltan should be slightly changed to:
Source => Target = (BGColor + Source) =
Target.R = ((1 - Source.A) * BGColor.R) + (Source.A * Source.R)
Target.G = ((1 - Source.A) * BGColor.G) + (Source.A * Source.G)
Target.B = ((1 - Source.A) * BGColor.B) + (Source.A * Source.B)
This changed version works fine for me, because in prev. version rgba(0,0,0,0) with matte rgb(ff,ff,ff) will be changed to rgb(0,0,0).
The fastest was would be to use a library that implements the conversion for you rather than writing it yourself. Which platform[s] are you targeting?
If you insist on writing it yourself for some reason, write a simple and correct version first. Use that. If the performance is inadequate, then you can think about optimizing it. In general, this sort of conversion is best done using vector permutes, but the exact optimal sequence varies depending on the target architecture.
How tricky do you want it? You could set it up to copy a 4-byte word at a time, which might be a bit faster on some 32-bit systems:
void fast_unpack(char* rgba, const char* rgb, const int count) {
if(count==0)
return;
for(int i=count; --i; rgba+=4, rgb+=3) {
*(uint32_t*)(void*)rgba = *(const uint32_t*)(const void*)rgb;
}
for(int j=0; j<3; ++j) {
rgba[j] = rgb[j];
}
}
The extra case on the end is to deal with the fact that the rgb array is missing a byte. You could also make it a bit faster using aligned moves and SSE instructions, working in multiples of 4 pixels at a time. If you're feeling really ambitious, you can try even more horribly obfuscated things like prefetching a cache line into the FP registers, for example, then blitting it across to the other image all at once. Of course the mileage you get out of these optimizations is going to be highly dependent on the specific system configuration you are targetting, and I would be really skeptical that there is much benefit at all to doing any of this instead of the simple thing.
And my simple experiments confirm that this is indeed a little bit faster, at least on my x86 machine. Here is a benchmark:
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <time.h>
void fast_unpack(char* rgba, const char* rgb, const int count) {
if(count==0)
return;
for(int i=count; --i; rgba+=4, rgb+=3) {
*(uint32_t*)(void*)rgba = *(const uint32_t*)(const void*)rgb;
}
for(int j=0; j<3; ++j) {
rgba[j] = rgb[j];
}
}
void simple_unpack(char* rgba, const char* rgb, const int count) {
for(int i=0; i<count; ++i) {
for(int j=0; j<3; ++j) {
rgba[j] = rgb[j];
}
rgba += 4;
rgb += 3;
}
}
int main() {
const int count = 512*512;
const int N = 10000;
char* src = (char*)malloc(count * 3);
char* dst = (char*)malloc(count * 4);
clock_t c0, c1;
double t;
printf("Image size = %d bytes\n", count);
printf("Number of iterations = %d\n", N);
printf("Testing simple unpack....");
c0 = clock();
for(int i=0; i<N; ++i) {
simple_unpack(dst, src, count);
}
c1 = clock();
printf("Done\n");
t = (double)(c1 - c0) / (double)CLOCKS_PER_SEC;
printf("Elapsed time: %lf\nAverage time: %lf\n", t, t/N);
printf("Testing tricky unpack....");
c0 = clock();
for(int i=0; i<N; ++i) {
fast_unpack(dst, src, count);
}
c1 = clock();
printf("Done\n");
t = (double)(c1 - c0) / (double)CLOCKS_PER_SEC;
printf("Elapsed time: %lf\nAverage time: %lf\n", t, t/N);
return 0;
}
And here are the results (compiled with g++ -O3):
Image size = 262144 bytes
Number of iterations = 10000
Testing simple unpack....Done
Elapsed time: 3.830000
Average time: 0.000383
Testing tricky unpack....Done
Elapsed time: 2.390000
Average time: 0.000239
So, maybe about 40% faster on a good day.
Since jQuery always seems to return the color like rgb(r, g, b) for elements that have no alpha, you could simply use:
$(this).css('background-color').replace(')', ', 0.75)').replace('rgb', 'rgba');
Just make sure the background color isn't rgba already:
var bg = $(this).css('background-color');
if(bg.indexOf('a') == -1){
var result = bg.replace(')', ', 0.75)').replace('rgb', 'rgba');
}
Another regex try http://jsfiddle.net/hc3BA/
var colour = 'rgb(123,123,123)',
new_col = colour.replace(/rgb/i, "rgba");
new_col = new_col.replace(/\)/i,',0.75)');