How do I create a transparent gif in Go? - go

when I encode a gif in Go, the background is all black. How do I make the background transparent?
Here is some code in my http handler. (w is the responseWriter)
m := image.NewRGBA(image.Rect(0, 0, pixelWidth, pixelHeight))
gif.Encode(w, m, &gif.Options{NumColors: 16})

I read the source of image/gif and found that there just has to be a transparent color on your palette.
var palette color.Palette = color.Palette{
image.Transparent,
image.Black,
image.White,
color.RGBA{0, 255, 0, 255},
color.RGBA{0, 100, 0, 255},
}
m := image.NewPaletted(image.Rect(0, 0, pixelWidth, pixelHeight), palette)
gif.Encode(w, m, &gif.Options{})

Related

Golang making RGBA images display weird colors

So I'm making some rgba images pixel by pixel following a certain pattern and saving them as png later on and noticed that when alpha channel es changed with certain colors it changes the whole pixel color when stored as png.
I made a test to show what is currently happening:
img := image.NewRGBA(image.Rect(0, 0, 250, 250))
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
f.Read(b)
img.SetRGBA(x, y, color.RGBA{
249,
214,
133,
255,
})
}
}
var buff bytes.Buffer
err := png.Encode(&buff, img)
if err != nil {
log.Println(err)
return
}
This will print an image of color #F9D685. But if I change alpha into 200 it will print another one with #6844BC and transparency instead of printing the original color with it's transparency.
Is there a way to solve this? I believe that it's because I'm missing something but can't really figure it out and didn't find anything similar to what's happening to me on google/here.
That one is simple:
go doc color.RGBA
package color // import "image/color"
type RGBA struct {
R, G, B, A uint8
}
RGBA represents a traditional 32-bit alpha-premultiplied color, having 8
bits for each of red, green, blue and alpha.
An alpha-premultiplied color component C has been scaled by alpha (A), so
has valid values 0 <= C <= A.
You might be looking for color.NRGBA.
(Always, really always, consult the documentation of the involved types and functions. Always.)

Why would TextOut() be using a different coordinate system than AlphaBlend()?

I'm trying to write a text overlay function that generates a semitransparent background with text on it in the top right hand corner of the viewport. I wrote a test MFC application project with mostly default settings (I don't remember exactly, but AFAIK, none of the settings should cause the problems I'm seeing).
Here is the code:
void DrawSemitransparentRect(CDC& destDC, CRect rect, float percentGrayBackground, COLORREF overlayColour, float overlayPercentOpaque)
{
rect.NormalizeRect();
CDC temp_dc; // Temp dc for semitransparent text background
temp_dc.CreateCompatibleDC(&destDC);
CBitmap layer; // Layer for semitransparent text background
layer.CreateCompatibleBitmap(&destDC, 1, 1);
CBitmap* pOldBitmap = temp_dc.SelectObject(&layer);
BLENDFUNCTION blendFunction = { AC_SRC_OVER, 0, 0, 0 };
auto DrawSemitransparentRectHelper = [&](COLORREF colour, float transparency)
{
temp_dc.SetPixel(0, 0, colour);
blendFunction.SourceConstantAlpha = BYTE(transparency * 255 / 100);
// Draw semitransparent background
VERIFY(destDC.AlphaBlend(rect.left, rect.top, rect.Width(), rect.Height()
, &temp_dc, 0, 0, 1, 1, blendFunction));
};
// Lighten up the area to make more opaque without changing overlay colour.
DrawSemitransparentRectHelper(RGB(255, 255, 255), percentGrayBackground);
// Draw overlay colour
DrawSemitransparentRectHelper(overlayColour, overlayPercentOpaque);
temp_dc.SelectObject(pOldBitmap);
}
void DrawOverlayText(CDC & dc, CFont &windowFont, CRect const& windowRectDP, CString const& overlayText, CRect* pBoundingRectDP)
{
static bool debug = true;
int savedDC = dc.SaveDC();
::SetMapMode(dc.GetSafeHdc(), MM_TWIPS);
// Reset the window and viewport origins to (0, 0).
CPoint windowOrg, viewportOrg;
::SetWindowOrgEx(dc.GetSafeHdc(), 0, 0, &windowOrg);
::SetViewportOrgEx(dc.GetSafeHdc(), 0, 0, &viewportOrg);
LOGFONT logFont;// = { 12 * 10, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 255, _T("Times New Roman") };
windowFont.GetLogFont(&logFont);
logFont.lfHeight = 12 * 10; // 12 point font? Why isn't this *20? TWIPS are 20ths of a point.
// Font for the overlay text
CFont font;
font.CreatePointFontIndirect(&logFont, &dc);
CFont* pOldFont = dc.SelectObject(&font);
// window rect in Logical Points
CRect windowRect(windowRectDP);
dc.DPtoLP(windowRect);
// Get text extent in Logical Points
CRect textRect;
dc.DrawText(overlayText, textRect, DT_CALCRECT);
// inflation rectangle to add pixels around text
CRect inflate(8, 0, 8, 4);
dc.DPtoLP(&inflate);
// Create the bounding rect on the right hand of the view, making it a few pixels wider.
CRect boundingRect(textRect);
if (!debug)
{
boundingRect.InflateRect(inflate);
}
boundingRect.NormalizeRect();
boundingRect += CPoint(windowRect.Width() - boundingRect.Width(), 0);
CRect boundingRectDP(boundingRect);
if (pBoundingRectDP || !debug)
{
// Get the bounding rect in device coordinates
dc.LPtoDP(boundingRectDP);
*pBoundingRectDP = boundingRectDP;
}
if (!debug)
{
// round the bottom corners of the text box by clipping it
CRgn clip;
boundingRectDP.NormalizeRect();
clip.CreateRoundRectRgn(
boundingRectDP.left + 1 // +1 needed to make rounding coner match more closely to bottom right coner
, boundingRectDP.top - boundingRectDP.Height() // Getting rid of top rounded corners
, boundingRectDP.right
, boundingRectDP.bottom + 1
, 16, 16 // rounding corner may have to be more dynamic for different DPI screens
);
::SelectClipRgn(dc.GetSafeHdc(), (HRGN)clip.GetSafeHandle());
clip.DeleteObject();
}
// Calculatte centre position of text
CPoint centrePos(
boundingRect.left + (boundingRect.Width() - textRect.Width()) / 2 + 1
, boundingRect.top + (boundingRect.Height() - textRect.Height()) / 2 + 1);
if (debug)
{
// in debug mode, output text and then put semitransparent bounding rect over it.
dc.SetBkMode(debug ? OPAQUE : TRANSPARENT);
dc.SetBkColor(RGB(255, 0, 0));
dc.SetTextColor(RGB(0, 0, 0));
dc.TextOut(centrePos.x, centrePos.y, overlayText);
DrawSemitransparentRect(dc, boundingRect, 60, RGB(0, .25 * 255, .75 * 255), 40);
}
else
{
// 2 pixel offset in Logical Points
CPoint textShadowOffset(2, 2);
dc.DPtoLP(&textShadowOffset);
// in !debug mode, output semitransparent bounding rect and then put text over it.
DrawSemitransparentRect(dc, boundingRect, 60, RGB(0, .25 * 255, .75 * 255), 40);
dc.SetBkMode(debug ? OPAQUE : TRANSPARENT);
dc.SetTextColor(RGB(0, 0, 0));
dc.TextOut(centrePos.x, centrePos.y, overlayText);
dc.SetTextColor(RGB(255, 255, 255));
dc.TextOut(centrePos.x - textShadowOffset.x, centrePos.y - textShadowOffset.y, overlayText);
}
// Restore DC's state
dc.SelectObject(pOldFont);
dc.RestoreDC(savedDC);
}
// OnPaint() function for CView derived class.
void COverlayOnCViewView::OnPaint()
{
CPaintDC dc(this); // device context for painting
CString m_overlayText = _T("abcdefg ABCDEFG");
CFont windowFont;
LOGFONT logFont = { -12, 0, 0, 0, 400, 0, 0, 0, DEFAULT_CHARSET, 0, 0, CLEARTYPE_QUALITY, 0, _T("Segoe UI") };
windowFont.CreatePointFontIndirect(&logFont, &dc);
CRect windowRect;
GetClientRect(windowRect);
DrawOverlayText(dc, windowFont, windowRect, m_overlayText, nullptr);
}
Now, this works perfectly well in the default project, where I get the following:
But when I put it into another preexisting project, I get this:
You can see that the text is actually positioned above the translucent rectangle.
If I move the rectangle down the height of the text box, by changing
boundingRect += CPoint(windowRect.Width() - boundingRect.Width(), 0);
to
boundingRect += CPoint(windowRect.Width() - boundingRect.Width(), textRect.Height());
I get:
It's like the text function is specifying the bottom left corner rather than the top left corner for placement.
I wrote the free functions so that it should work with any DC, even if that DC has had its coordinate system manipulated, but perhaps I've forgotten to reset something?
The default project is using MFC 14.0.24212.0, but the project I tried to import this code into is using MFC 12.0.21005.1. Could that be an issue? I'm not sure how to change the default project to use the earlier version of MFC to test that.
Edit
Note that in the default project, I could have put the code into the OnDraw() function like this:
void COverlayOnCViewView::OnDraw(CDC* pDC)
{
COverlayOnCViewDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
// TODO: add draw code for native data here
CString m_overlayText = _T("abcdefg ABCDEFG");
CFont windowFont;
LOGFONT logFont = { -12, 0, 0, 0, 400, 0, 0, 0, DEFAULT_CHARSET, 0, 0, CLEARTYPE_QUALITY, 0, _T("Segoe UI") };
windowFont.CreatePointFontIndirect(&logFont, pDC);
CRect windowRect;
GetClientRect(windowRect);
DrawOverlayText(*pDC, windowFont, windowRect, m_overlayText, nullptr);
}
The only reason why I didn't was because the application I'm putting this into doesn't have one and I wanted to mimic that project as closely as possible. If you create a default application to test this, remember either to put the ON_WM_PAINT() macro in the MESSAGE MAP or use the OnDraw() function shown instead. They both seem to have the same results in the default project.

PIL: changing color isn't corrent if the image is resized before

I want to be able to recolor images after I have resized them, because if I do so before it takes too long since the originals are too big.
Codes:
fast, but bad:
img = Image.open("piimage.png")
img = img.resize((30,30), Image.ANTIALIAS)
pixdata = img.load()
for y in range(img.size[1]):
for x in range(img.size[0]):
if pixdata[x, y] == (255, 255, 255, 255):
pixdata[x, y] = (0, 0, 0, 255)
self.piimage = ImageTk.PhotoImage(img)
slow, but good:
img = Image.open("piimage.png")
pixdata = img.load()
for y in range(img.size[1]):
for x in range(img.size[0]):
if pixdata[x, y] == (255, 255, 255, 255):
pixdata[x, y] = (0, 0, 0, 255)
img = img.resize((30,30), Image.ANTIALIAS)
self.piimage = ImageTk.PhotoImage(img)
I' ve found this code here on stackoverflow, but that sample didn't have resize, but I need it since I use them for small buttons. The only difference between the upper and the lower is the position of the resize command.
Why does this happen and how to work around it ?
Image showing results:
The white pixels are not precisely white anymore after resizing with ANTIALIAS. Therefore, your if statement should not be too strict anymore. Better is to change it to something like this:
white_threshold = 208
if pixdata[x, y][0] <= white_threshold and
pixdata[x, y][1] <= white_threshold and
pixdata[x, y][2] <= white_threshold:
pixdata[x, y] = (0, 0, 0, 255)
You then might want to play around with the threshold value.
Sidenote: I am pretty sure there are faster ways than a double for loop to loop over all the pixels, although I don't recall them from the top of my head.
I have found a satisfactory solution:
def makeimage(self):
img = Image.open("piimagefunny.png")
img = img.resize((100,100))
pixdata = img.load()
for y in range(img.size[1]):
for x in range(img.size[0]):
if pixdata[x, y] <= (255, 255, 255, 255) and pixdata[x, y] > (0, 0, 0, 10):
pixdata[x, y] = (128, 128, 128)
img = img.resize((30,30), Image.ANTIALIAS)
self.piimage = ImageTk.PhotoImage(img)
The difference is to resize it before, no need for ANTIALIAS, but bigger than what you want, so that later the ANTIALIAS has some pixels to work with, also the above example recolors any color of the image.

Overlaying pictures in one image

Is it possible to merge two or more different bmp-pictures of the same size into one by overlaying on top of each other? The same way it was done in Windows XP MS Paint: pasting one picture in another, with secondary color being transparent.
You can use Transparent property of TBitmap to that effect. Since your bitmaps have a black border, automatic transparent color (first pixel of image data) wouldn't work and you need to also set the TransparentColor property to 'clWhite'.
var
bmp1, bmp2: TBitmap;
begin
bmp1 := TBitmap.Create;
bmp1.LoadFromFile('...\test1.bmp');
bmp2 := TBitmap.Create;
bmp2.LoadFromFile('...\test2.bmp');
// bmp2.PixelFormat := pf24bit; // with 32 bit images I need this, don't know why
bmp2.Transparent := True;
bmp2.TransparentColor := clWhite;
bmp1.Canvas.Draw(0, 0, bmp2); // draw bmp2 over bmp1
// this is how the merged image looks like
Canvas.Draw(0, 0, bmp1);
..
In case of the second bitmap is black-and-white, you can use it as a mask in a raster operation with BitBlt ( bit-block transfer), as follows:
Windows.BitBlt(Bmp3.Canvas.Handle, 0, 0, Bmp3.Width, Bmp3.Height,
Bmp1.Canvas.Handle, 0, 0, SRCCOPY);
Windows.BitBlt(Bmp3.Canvas.Handle, 0, 0, Bmp3.Width, Bmp3.Height,
Bmp2.Canvas.Handle, 0, 0, SRCAND);

OpenGL ES 2.0 Convert int[] to GLubyte[]

The following works as a texture...
GLubyte bytePix[4 * 3] ={
255, 0, 0, //red
0, 255, 0, //green
0, 0, 255, //blue
255, 255, 0 //yellow
};
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, pixelWidth, pixelHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, pbytePix);
Problem is I am passing in my BMP as an int[] so I would need something more like this...
int bytePix[4 * 3] ={
255, 0, 0, //red
0, 255, 0, //green
0, 0, 255, //blue
255, 255, 0 //yellow
};
But this doesn't show the same result.
My question is how do I convert the latter into a GLubtye[] or some other recognizable format.
On your platform, sizeof(int) clearly isn't equal to sizeof(GLubyte). I guess the immediate question is — why are you using int? It's likely just to be a huge waste of space if you're storing only values in the range 0–255.
You can't just use GL_INT or GL_UNSIGNED_INT in place of GL_UNSIGNED_BYTE, even if they are the same size as your int as you're using only a byte's range within each integer.
That aside, you'll notice that glTexImage2D doesn't have a stride parameter unlike glVertexAttribPointer and most of the other functions that exist primarily to provide data. So even though you have your values within bytes and those bytes are a predictable space apart, OpenGL can't pull them apart and repack them for you.
So the easiest option is to do it yourself:
void glTexImage2DWithStride(..., GLsizei stride, ...)
{
// the following is written to assume GL_RGB; adapt as necessary
GLubyte *byteBuffer = (GLubyte *)malloc(width * height * 3);
for(int c = 0; c < width * height * 3; c++)
byteBuffer[c] = originalBuffer[c];
glTexImage2D(..., byteBuffer, ...);
free(byteBuffer);
}
Failing that, supposing your int is four times as large as a byte, you could upload the original as an RGBA texture that's four times as large as its real size, then shrink it down in a shader, combining the .r or .as (as per your endianness) into the correct output channels.
Since ubyte and int are different in size, I guess you have to create a new ubyte array and convert explicitly element by element with a for loop, before passing it to OpenGL.

Resources