Related
I try to get images using off screen rendering on Android NDK, with OpenGL ES 2.0.
Android Version : 4.4.2
Device : Samsung Galaxy Tab SM-T705
I make a simple button example in Android
If you push the button, The program makes CreatePbufferWindow in NDK
The Program draw a Triangle.
glReadPixels() gets a result image.
and save the Bitmap Class
The program draws the result image in Android window.
I don't want to cover the bar and button
and I don't expect to diplay OpenGL ES View.
NDK C code
#include <jni.h>
#include <android/log.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <EGL/eglplatform.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <android/bitmap.h>
#include "tga.h"
#include "jpge.h"
#define LOG_TAG "libgl2jni"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
//static void printGLString(const char *name, GLenum s) {
// const char *v = (const char *) glGetString(s);
// LOGI("GL %s = %s\n", name, v);
//}
static void checkGlError(const char* op) {
for (GLint error = glGetError(); error; error
= glGetError()) {
LOGI("after %s() glError (0x%x)\n", op, error);
}
}
static const char gVertexShader[] =
"attribute vec4 vPosition;\n"
"void main() {\n"
" gl_Position = vPosition;\n"
"}\n";
static const char gFragmentShader[] =
"precision mediump float;\n"
"void main() {\n"
" gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
"}\n";
/**
* Initialize an EGL context for the current display.
*/
static int engine_init_display(int width, int height) {
// initialize OpenGL ES and EGL
/*
* Here specify the attributes of the desired configuration.
* Below, we select an EGLConfig with at least 8 bits per color
* component compatible with on-screen windows
*/
const EGLint attribs[] = {
//EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_NONE
};
EGLint w, h, dummy, format;
EGLint numConfigs;
EGLConfig config;
EGLSurface surface;
EGLContext context;
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
checkGlError("eglGetDisplay");
eglInitialize(display, 0, 0);
checkGlError("eglInitialize");
/* Here, the application chooses the configuration it desires. In this
* sample, we have a very simplified selection process, where we pick
* the first EGLConfig that matches our criteria */
eglChooseConfig(display, attribs, &config, 1, &numConfigs);
checkGlError("eglChooseConfig");
/* EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is
* guaranteed to be accepted by ANativeWindow_setBuffersGeometry().
* As soon as we picked a EGLConfig, we can safely reconfigure the
* ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID. */
eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format);
checkGlError("eglGetConfigAttrib");
// using PexelBuffer
EGLint attribList[] =
{
EGL_WIDTH, width,
EGL_HEIGHT, height,
EGL_LARGEST_PBUFFER, EGL_TRUE,
EGL_NONE
};
surface = eglCreatePbufferSurface(display, config, attribList);
checkGlError("eglCreatePbufferSurface");
// surface = eglCreateWindowSurface(display, config, engine->app->window, NULL);
const EGLint attrib_list[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
context = eglCreateContext(display, config, NULL, attrib_list);
checkGlError("eglCreateContext");
if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) {
// LOGW("Unable to eglMakeCurrent");
return -1;
}
// eglQuerySurface(display, surface, EGL_WIDTH, &w);
// eglQuerySurface(display, surface, EGL_HEIGHT, &h);
// Initialize GL state.
glEnable(GL_CULL_FACE);
// glDisable(GL_DEPTH_TEST);
glEnable(GL_DEPTH_TEST);
return 0;
}
GLuint loadShader(GLenum shaderType, const char* pSource) {
GLuint shader = glCreateShader(shaderType);
if (shader) {
glShaderSource(shader, 1, &pSource, NULL);
glCompileShader(shader);
GLint compiled = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (!compiled) {
GLint infoLen = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
if (infoLen) {
char* buf = (char*) malloc(infoLen);
if (buf) {
glGetShaderInfoLog(shader, infoLen, NULL, buf);
LOGE("Could not compile shader %d:\n%s\n",
shaderType, buf);
free(buf);
}
glDeleteShader(shader);
shader = 0;
}
}
}
return shader;
}
GLuint createProgram(const char* pVertexSource, const char* pFragmentSource) {
GLuint vertexShader = loadShader(GL_VERTEX_SHADER, pVertexSource);
if (!vertexShader) {
return 0;
}
GLuint pixelShader = loadShader(GL_FRAGMENT_SHADER, pFragmentSource);
if (!pixelShader) {
return 0;
}
GLuint program = glCreateProgram();
if (program) {
glAttachShader(program, vertexShader);
checkGlError("glAttachShader");
glAttachShader(program, pixelShader);
checkGlError("glAttachShader");
glLinkProgram(program);
GLint linkStatus = GL_FALSE;
glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
if (linkStatus != GL_TRUE) {
GLint bufLength = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
if (bufLength) {
char* buf = (char*) malloc(bufLength);
if (buf) {
glGetProgramInfoLog(program, bufLength, NULL, buf);
LOGE("Could not link program:\n%s\n", buf);
free(buf);
}
}
glDeleteProgram(program);
program = 0;
}
}
return program;
}
GLuint gProgram;
GLuint gvPositionHandle;
bool setupGraphics(int w, int h) {
// printGLString("Version", GL_VERSION);
// printGLString("Vendor", GL_VENDOR);
// printGLString("Renderer", GL_RENDERER);
// printGLString("Extensions", GL_EXTENSIONS);
LOGI("setupGraphics(%d, %d)", w, h);
gProgram = createProgram(gVertexShader, gFragmentShader);
if (!gProgram) {
LOGE("Could not create program.");
return false;
}
gvPositionHandle = glGetAttribLocation(gProgram, "vPosition");
checkGlError("glGetAttribLocation");
LOGI("glGetAttribLocation(\"vPosition\") = %d\n",
gvPositionHandle);
glViewport(0, 0, w, h);
checkGlError("glViewport");
return true;
}
GLfloat gTriangleVertices[] = { 1.0f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f,
-1.5f, 1.5f, 1.5f };
char* renderFrame(int width, int height) {
static float grey;
grey += 0.01f;
if (grey > 1.0f) {
grey = 0.0f;
}
// 크기 조정
for(int count = 0; count < 9; ++count)
{
gTriangleVertices[count] *= 0.8f;
}
char* pixelData = (char*)malloc(4 * width * height * sizeof(char));
for(int count = 0; count < width * height * 4; ++count) {
pixelData[count] = 0;
}
// 깊이 버퍼 활성화
glEnable(GL_DEPTH_TEST);
// 깊이 버퍼 초기화
glClearDepthf(1.F);
// glClearColor(grey, grey, grey, 1.0f);
glClearColor(1.0, 0.0, 0.0, 1.0f);
checkGlError("glClearColor");
glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
checkGlError("glClear");
glUseProgram(gProgram);
checkGlError("glUseProgram");
glVertexAttribPointer(gvPositionHandle, 3, GL_FLOAT, GL_FALSE, 0, gTriangleVertices);
checkGlError("glVertexAttribPointer");
glEnableVertexAttribArray(gvPositionHandle);
checkGlError("glEnableVertexAttribArray");
glDrawArrays(GL_TRIANGLES, 0, 3);
checkGlError("glDrawArrays");
glReadPixels(
0, 0,
width, height,
GL_RGBA,
GL_UNSIGNED_BYTE,
pixelData
);
// int jpgCount = 0;
// for(int count = 0; count < width * height * 4; ++count) {
// if (count % 4 != 3) {
// pixelJpgData[jpgCount] = pixelData[count];
// ++jpgCount;
// }
// }
// tgaGrabScreenSeries("/storage/emulated/0/Pictures/CTgaTest", 0, 0, width, height);
jpge::compress_image_to_jpeg_file(
"/storage/emulated/0/Pictures/CJpgTest.jpg",
width, height,
4,
(jpge::uint8*)pixelData
);
// LOGI("%s", (char const *)pixelData);
// for(int count = 0; count < width*height*4; ++count) {
// LOGI("%x", pixelData[count]);
// }
return pixelData;
// if(pixelData) {
// free(pixelData);
//// delete[] pixelData;
// }
}
int
decodeMemory(JNIEnv* env, const void* data, size_t len, jobject* bitmap)
{
jclass clazz = env->FindClass("android/graphics/BitmapFactory");
if (env->ExceptionCheck()) {
env->ExceptionClear();
return 2;
}
jmethodID mid = env->GetStaticMethodID(clazz, "decodeArray",
"([BII)Landroid/graphics/Bitmap;");
if (env->ExceptionCheck()) {
env->ExceptionClear();
return 2;
}
jbyteArray jarray = env->NewByteArray(len);
env->SetByteArrayRegion(jarray, 0, len, (jbyte*)data);
*bitmap = env->CallStaticObjectMethod(clazz, mid, jarray, 0, len);
return 1;
}
extern "C"
{
JNIEXPORT void JNICALL Java_com_javacodegeeks_android_buttonexample_GL2JNILib_init
(JNIEnv * env, jobject obj, jint width, jint height)
{
engine_init_display(width, height);
setupGraphics(width, height);
}
}
extern "C"
{
JNIEXPORT void JNICALL Java_com_javacodegeeks_android_buttonexample_GL2JNILib_step
(JNIEnv * env, jobject obj, jobject jBitmap, jint width, jint height)
//(JNIEnv * env, jobject obj, jint width, jint height)
{
char* pixelData = NULL;
pixelData = renderFrame(width, height);
// renderFrame(width, height);
decodeMemory(env, pixelData, width * height * 4, &jBitmap);
if (pixelData)
{
free(pixelData);
}
}
}
java Code
package com.javacodegeeks.android.buttonexample;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Environment;
import android.util.Log;
import android.view.View;
class GL2View extends View
{
public GL2View(Context context, int w, int h) {
super(context);
// TODO Auto-generated constructor stub
width = w;
height = h;
// //OpenGL 테스트 구현부 시작 /////////////////////////////////////////////////////
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
GL2JNILib.init(width, height);
mCanvas = new Canvas();
mCanvas.setBitmap(mBitmap);
// //OpenGL 테스트 구현부 끝 /////////////////////////////////////////////////////
mPaint = new Paint();
}
/* Image SDCard Save (input Bitmap -> saved file JPEG)
* Writer intruder(Kwangseob Kim)
* #param bitmap : input bitmap file
* #param folder : input folder name
* #param name : output file name
*/
public static void saveBitmaptoJpeg(Bitmap bitmap,String folder, String name){
// String ex_storage =Environment.getExternalStorageDirectory().getAbsolutePath(); // 절대 경로
String ex_storage = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath(); // 이미지 경로
// Get Absolute Path in External Sdcard
String foler_name = "/"+folder+"/";
String file_name = name+".jpg";
String string_path = ex_storage+foler_name;
Log.d(VIEW_LOG_TAG, ex_storage);
File file_path;
try{
file_path = new File(string_path);
if(!file_path.isDirectory()){
file_path.mkdirs();
}
FileOutputStream out = new FileOutputStream(string_path+file_name);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
out.close();
}catch(FileNotFoundException exception){
Log.e("FileNotFoundException", exception.getMessage());
}catch(IOException exception){
Log.e("IOException", exception.getMessage());
}
}
public void SaveBitmapToSDcard()
{
// File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
saveBitmaptoJpeg(mBitmap, "./", "JavaTest");
}
// #Override
// protected void onSizeChanged(int w, int h, int oldw, int oldh)
// {
// mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
// mCanvas = new Canvas();
// mCanvas.setBitmap(mBitmap);
//
// testDrawing();
// }
//
// private void testDrawing()
// {
// mCanvas.drawColor(Color.WHITE);
// mPaint.setColor(Color.RED);
// mCanvas.drawRect(100, 100, 200, 200, mPaint);
// }
//
#Override
protected void onDraw(Canvas canvas)
{
GL2JNILib.step(mBitmap, width, height);
// GL2JNILib.step(width, height);
if(mBitmap != null)
{
canvas.drawBitmap(mBitmap, 0, 0, null);
}
SaveBitmapToSDcard();
}
private Bitmap mBitmap;
private Canvas mCanvas;
private Paint mPaint;
int width;
int height;
}
Native Call
//package com.android.gl2jni;
package com.javacodegeeks.android.buttonexample;
import android.graphics.Bitmap;
// Wrapper for native library
public class GL2JNILib {
static {
System.loadLibrary("gl2jni");
}
/**
* #param width the current view width
* #param height the current view height
*/
public static native void init(int width, int height);
public static native void step(Bitmap bitmap, int width, int height);
// public static native void step(int width, int height);
}
Buttom Image
Result Image
If you are asking how to export pixel datas from only Offscreen-FBO where you draw objects, you should have bind offscreen-FBO before calling glreadpixels()
Problem 1. You never bind & unbind FBOs. Thus, you draw objects on-Screen buffer.
Problem 2. You never generate offscreen-FBO and bind it before glreadpixels();
Solution. generate a offscreen-FBO, and bind it before drawing objects, and bind it before calling glreadpixels();
Update
It is codes to generate FBO and RBO and how to attach rbo to fbo
glGenFramebuffers(1, &fbo[object_id]);
glBindFramebuffer(GL_FRAMEBUFFER, fbo[object_id]);
glGenRenderbuffers(1, &rboColor[object_id]);
glBindRenderbuffer(GL_RENDERBUFFER, rboColor[object_id]);
glRenderbufferStorage(GL_RENDERBUFFER,GL_RGBA8_OES, width, height);
glBindFramebuffer(GL_FRAMEBUFFER, fbo[object_id]);
// Attach ColorRenderbuffers
glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_RENDERBUFFER,rboColor[object_id]);
This is all the code I have. The functions begining with zx are just so that when I'm done I can quickly put together a custom library based on those functions. The function I need help with is zxResizeGL which is used only on the WM_SIZE event atm.
#include <windows.h>
#include <gl/gl.h>
LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);
void EnableOpenGL(HWND hwnd, HDC*, HGLRC*);
void DisableOpenGL(HWND, HDC, HGLRC);
/* Necessary value we need access to */
EXTERN_C IMAGE_DOS_HEADER __ImageBase;
#define ZX_THIS_INSTANCE ((HINSTANCE)&__ImageBase)
/* We define 2 here so dev can retrieve the FPN b4 it is multiplied by 100 */
#define ZX__PERCENT( NUM, OF ) ( ( NUM ) / ( OF ) )
#define ZX_PERCENT( NUM, OF ) ( ZX__PERCENT( NUM, OF ) * 100 )
WNDCLASSEX zxGetOSWindowClassEX( void );
BOOL zxInitOSClasses( void );
HWND zxNewOSWindow( int w, int h, int x, int y, char const *text );
HDC hDC = NULL;
HGLRC hRC = NULL;
void zxResizeGL(int width, int height )
{
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, width, 0, height, -1.0, 1.0);
glFlush();
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
}
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
HWND hwnd;
MSG msg;
BOOL bQuit = FALSE;
float theta = 0,
width = 256,
height = width,
w = ZX__PERCENT( width - 20, width ),
h = w;
if ( !zxInitOSClasses() )
return 0;
hwnd = zxNewOSWindow( 480, 480, 0, 0, "OpenGL Sample" );
ShowWindow(hwnd, nCmdShow);
/* enable OpenGL for the window */
EnableOpenGL(hwnd, &hDC, &hRC);
/* program main loop */
while (!bQuit)
{
/* check for messages */
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
/* handle or dispatch messages */
switch ( msg.message )
{
case WM_QUIT:
bQuit = TRUE;
break;
case WM_SIZE:
/* This is where I'm trying to peform the resize */
width = LOWORD( msg.lParam );
height = HIWORD( msg.lParam );
w = ZX__PERCENT( width - 20, width );
h = ZX__PERCENT( height - 20, height );
zxResizeGL( width, height );
break;
default:
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
else
{
/* OpenGL animation code goes here */
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
glPushMatrix();
glRotatef(theta, 0.0f, 0.0f, 1.0f);
glBegin(GL_TRIANGLES);
glColor3f(1.0f, 0.0f, 0.0f); glVertex2f( 0.0f, h );
glColor3f(0.0f, 1.0f, 0.0f); glVertex2f( w, -h );
glColor3f(0.0f, 0.0f, 1.0f); glVertex2f( -w, -h );
glEnd();
glPopMatrix();
SwapBuffers(hDC);
theta += 1.0f;
Sleep (1);
}
}
/* shutdown OpenGL */
DisableOpenGL(hwnd, hDC, hRC);
/* destroy the window explicitly */
DestroyWindow(hwnd);
return msg.wParam;
}
The defines for the functions broken off from main code.
WNDCLASSEX zxGetOSWindowClassEX( void )
{
WNDCLASSEX wx = {0};
wx.cbSize = sizeof( WNDCLASSEX );
wx.style = CS_OWNDC;
wx.lpfnWndProc = WindowProc;
wx.hInstance = ZX_THIS_INSTANCE;
wx.hIcon = LoadIcon( NULL, IDI_APPLICATION );
wx.hCursor = LoadCursor( NULL, IDC_ARROW );
wx.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wx.lpszClassName = "zxOSWindow";
wx.hIconSm = LoadIcon( NULL, IDI_APPLICATION );
return wx;
}
BOOL zxInitOSClasses( void )
{
WNDCLASSEX wx = zxGetOSWindowClassEX();
if ( !RegisterClassEx( &wx ) )
return 0;
return 1;
}
HWND zxNewOSWindow( int w, int h, int x, int y, char const *text )
{
WNDCLASSEX wx = zxGetOSWindowClassEX();
return CreateWindowEx(
0, wx.lpszClassName, text,
WS_OVERLAPPEDWINDOW,
x, y, w, h, NULL, NULL,
wx.hInstance, NULL );
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CLOSE:
PostQuitMessage(0);
break;
case WM_DESTROY:
return 0;
case WM_KEYDOWN:
{
switch (wParam)
{
case VK_ESCAPE:
PostQuitMessage(0);
break;
}
}
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
void EnableOpenGL(HWND hwnd, HDC* hDC, HGLRC* hRC)
{
PIXELFORMATDESCRIPTOR pfd;
int iFormat;
/* get the device context (DC) */
*hDC = GetDC(hwnd);
/* set the pixel format for the DC */
ZeroMemory(&pfd, sizeof(pfd));
pfd.nSize = sizeof(pfd);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW |
PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 24;
pfd.cDepthBits = 16;
pfd.iLayerType = PFD_MAIN_PLANE;
iFormat = ChoosePixelFormat(*hDC, &pfd);
SetPixelFormat(*hDC, iFormat, &pfd);
/* create and enable the render context (RC) */
*hRC = wglCreateContext(*hDC);
wglMakeCurrent(*hDC, *hRC);
}
void DisableOpenGL (HWND hwnd, HDC hDC, HGLRC hRC)
{
wglMakeCurrent(NULL, NULL);
wglDeleteContext(hRC);
ReleaseDC(hwnd, hDC);
}
Microsoft says WM_SIZE messages are sent (not posted) and thus come in via WindowProc().
PeekMessage() can only retrieve posted messages.
Handle WM_SIZE in your WindowProc().
After having tried a break point I noticed I was not receiving the WM_SIZE event, will investigate alternate ways to perform resize. For now I will put the zxResizeGL in the drawing loop
I'm trying to add bitmaps to a popup menu.
The problem I have is that I create my bitmaps from ICONs dynamically, and I have problems for 24bit color icons vs 24+alpha (32bit) color icons.
It seems that the bitmaps are only always drawn correctly when they are in 32bpp (including alpha channel).
Here's my (test) code:
void ConvertIconToBitmap(CBitmap& bmpObj, HICON hIcon, int cx, int cy, BOOL useDIB) {
CClientDC screenDC(NULL);
CBitmap bmpTmp;
HANDLE_API_FAILURE( bmpTmp.CreateCompatibleBitmap(&screenDC, cx, cy) );
CDC memDC;
HANDLE_API_FAILURE( memDC.CreateCompatibleDC(&screenDC) );
CBitmap* pOldBmp = memDC.SelectObject(&bmpTmp);
HANDLE_API_FAILURE( ::DrawIconEx( memDC.GetSafeHdc(), 0, 0, hIcon, cx, cy, 0, NULL, DI_NORMAL) );
memDC.SelectObject( pOldBmp );
HBITMAP hDibBmp = (HBITMAP)::CopyImage((HANDLE)(HBITMAP)bmpTmp, IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE | LR_CREATEDIBSECTION);
HANDLE_API_FAILURE( hDibBmp );
if (useDIB) {
HANDLE_API_FAILURE( bmpObj.Attach(hDibBmp) ); // works for 32bit/transparanecy
} else {
HANDLE_API_FAILURE( bmpObj.Attach(bmpTmp.Detach()) ); // works for 24bit
}
}
...
CMenu menu;
menu.LoadMenu(IDR_POPUPMENU);
CMenu* pMenu = menu.GetSubMenu(0);
int cx, cy;
cx = cy = GetSystemMetrics(SM_CYMENU);
MENUITEMINFO mii;
mii.cbSize = sizeof mii;
mii.fMask = MIIM_BITMAP;
HICON pwdIco = ExtractIcon(NULL/*=current instance*/, L"path/to/file.exe", 0);
// If icon is 32bit-alpha this works, but doesn't work with 24bit icon
CBitmap bmpTop;
ConvertIconToBitmap(bmpTop, pwdIco, cx, cy, TRUE);
mii.hbmpItem = bmpTop;
pMenu->SetMenuItemInfo(ID_MENU_TOP, &mii, FALSE);
// If icon is 32bit-alpha transparent areas are shown in black, but 24bit icon is also displayed correctly
CBitmap bmpApp;
ConvertIconToBitmap(bmpApp, pwdIco, cx, cy, FALSE);
HBITMAP bmpTransp = bmpApp;
mii.hbmpItem = bmpTransp;
pMenu->SetMenuItemInfo(ID_MENU_TEST, &mii, FALSE);
this->ClientToScreen(&point);
pMenu->TrackPopupMenu(nFlags, point.x, point.y, this);
Since I need to dynamically populate the menu entries images from existing file's icons, I can't just statically fiddle out the bitmaps and use these instead of icons.
What are the requirements for the HBITMAP passed in the hbmpItem member of the MENUITEMINFO structure passed to SetMenuItemInfo ???
And how do I make sure the bitmap "created" with DrawIconEx does fulfill these requirements?
Oh, and to my great pleasure I just learned that 32-bit alpha-blended menu item bitmaps don't work at under Windows XP (transparent=black there), so I'll have to map transparency myself somehow it seems. (Possibly by replacing some color with GetSysColor(COLOR_MENU).)
I have a class that extends from ButtonField :
class BitmapButtonField extends ButtonField
{
private Bitmap _bitmap;
private int _buttonWidth;
private int _buttonHeight;
BitmapButtonField(Bitmap bitmap, int buttonWidth, int buttonHeight, long style)
{
super(style);
_buttonWidth = buttonWidth;
_buttonHeight = buttonHeight;
_bitmap = bitmap;
}
public int getPreferredHeight()
{
return _buttonHeight;
}
public int getPreferredWidth()
{
return _buttonWidth;
}
protected void layout(int width, int height)
{
setExtent(Math.min( width, getPreferredWidth()), Math.min( height, getPreferredHeight()));
}
protected void paint(Graphics graphics)
{
// THIS IS NOT CENTERED
int x = (getPreferredWidth() - _bitmap.getWidth()) >> 1;
graphics.drawBitmap(x, 0, _bitmap.getWidth(), _bitmap.getHeight(), _bitmap, 0, 0);
// THIS IS NOT LEFTMOST, TOPMOST
graphics.drawBitmap(0, 0, _bitmap.getWidth(), _bitmap.getHeight(), _bitmap, 0, 0);
}
}
If you could see from my comments on the paint method, clearly the ButtonField 0,0 position is not exactly in leftmost and topmost corner of the button, somehow it is padded with unknown offset, so this makes centering the image is difficult.
But if I extend from a Field class, the problem goes away, but I need to keep extending from ButtonField since I need the ButtonField border and focus style (blue-white rounded rectangle), I just need to display centered image on a ButtonField, and still retaining all of the standard ButtonField attributes.
Is there a way to eliminate the padded offset in paint method on a ButtonField? I've tried setPadding and setMargin but no such luck. Thanks a lot!
In paint() to define x offset for image there is a code:
int x = (getPreferredWidth() - _bitmap.getWidth()) >> 1;
It is ok, still button can have other size, because of:
protected void layout(int width, int height)
{
setExtent(Math.min( width, getPreferredWidth()),
Math.min( height, getPreferredHeight()));
}
Try to use
protected void layout(int width, int height)
{
setExtent(getPreferredWidth(), getPreferredHeight());
}
I am currently playing with Qt trying to set up a small particle system. Therein I've subclassed the GLWidget and hacked away at it. Everything was going well until I made some unknown change and now the widget only repaints when I move the mouse (it should be doing it all the time due to the QTimer I have firing). Relevant code:
OpenGLWidget.h
class OpenGLWidget : public QGLWidget {
Q_OBJECT
public:
OpenGLWidget(QWidget * parent = 0);
~OpenGLWidget();
public slots:
void toggleEmitter();
protected:
void initializeGL();
void paintGL();
void resizeGL(int width, int height);
QSize minimumSizeHint() const;
QSize sizeHint() const;
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
protected slots:
void timeOut();
private:
void testFunc(Particle & p, unsigned int t);
QTime time;
QTimer timer;
Emitter emitter;
Relevant code from the .cpp
// in the constructor
time.start();
connect(&timer, SIGNAL(timeout()), this, SLOT(timeOut()));
timer.start(0);
void OpenGLWidget::initializeGL() {
GLuint tex = 0;
qglClearColor(bgColor);
glEnable(GL_DEPTH_TEST);
glClearDepth(1.0f);
glDepthFunc(GL_LEQUAL);
glShadeModel(GL_SMOOTH);
glDisable(GL_CULL_FACE);
glEnable(GL_TEXTURE_2D);
glEnable( GL_BLEND );
glBlendFunc( GL_SRC_ALPHA, GL_ONE); // _MINUS_SRC_ALPHA );
glPointSize(3.0f);
tex = bindTexture(QPixmap(QString("testparticle2.png")), GL_TEXTURE_2D);
emitter = Emitter(Vector(0, 0, 0.0f), tex, 50, fastdelegate::FastDelegate2<Particle &, unsigned int>(this, &OpenGLWidget::testFunc));
}
void OpenGLWidget::paintGL() {
makeCurrent();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
mainCam.SetView();
glRotatef(xRot, 1, 0, 0);
glRotatef(yRot, 0, 1, 0);
emitter.Process(time.elapsed());
totalTime += time.elapsed();
time.restart();
}
void OpenGLWidget::resizeGL(int width, int height) {
contextWidth = width;
contextHeight = height;
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(55.0f, width / (float) height, 0.01f, 100.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void OpenGLWidget::timeOut() {
updateGL();
}
void OpenGLWidget::mousePressEvent(QMouseEvent *event) {
lastX = event->pos().x();
lastY = event->pos().y();
}
void OpenGLWidget::mouseMoveEvent(QMouseEvent *event) {
int dx = event->x() - lastX;
int dy = event->y() - lastY;
if (event->buttons() & Qt::LeftButton) {
xRot += 3 * dy;
yRot += 3 * dx;
} else if (event->buttons() & Qt::RightButton) {
xRot += 3 * dy;
yRot += 3 * dx;
}
lastX = event->pos().x();
lastY = event->pos().y();
}
A QTimer with 0 second time out only fires when the event loop gets the control. I suspect this is not the case. However, when you move the mouse, the event loop processes some events (again) and then delivers the pending timer event(s). Maybe you need to invoke processEvents() in your update loop to make sure that the pending timer event from QTimer gets processed.
considering that updateGL() calls glDraw() which calls paintGL(), you'd think the timer should be calling the same functionality as your mousemove event.
Are you sure the timer is actually ticking?
BTW, there is no need to call makeCurrent() in paintGL().
edit: after the extra information was added:
QTimer says:
"As a special case, a QTimer with
timeout 0 times out as soon as all the
events in the window system's event
queue have been processed."
so, if you want it to go as fast as possible (though 10ms is probably the minimum on Windows and most x386-based systems), then start your timer with a value of 1 instead of 0. I assume that this is the problem, that its only ticking when it has finished reading off messages from the queue.
Timer simply can't work with less than 20ms. It's equals 0 for it. You may use simple loop with measuring time from one iteration to another. Measuring method probably should use API of your operation system.