LinuxQuestions.org
Share your knowledge at the LQ Wiki.
Go Back   LinuxQuestions.org > Blogs > rainbowsally
User Name
Password

Notices

Rate this Entry

QT, fast!! pixel (color) mixing

Posted 06-21-2013 at 08:22 AM by rainbowsally
Updated 06-21-2013 at 08:32 AM by rainbowsally

Today's feature
  • super fast color mixing ('lerp') for QT4
  • PRINT_AND_DO macro to show function and execute it.
  • 8 bit fixed point math.
  • A snippet of working code from the next lq-qt/mc2 lib.

Behind the scenes we are working on getting qwt lib to play nice with Designer. We're apparently over the hump with dials, which were very tricky!!

And so it's break time.

Let's talk a bit about fast color mixing, which isn't always an issue but can be when you are trying to draw intermediate color values in something like an animation or anything else that needs to be redrawn pixel by pixel in real-time.

If you look at what's involved in converting something like a Qt::darkblue to a pixel, which is an AARRGGBB uint value, you might be surprised at all the extra steps involved. I was.

The QT process involves creating a QColor, separating the color into alpha, red, green, and blue values, setting the pixel format field as 'rgb', and then recombining them into a QRgb type (which is a simple uint) by way of the 'QColor::rgb() function (call).

There are a lot of steps involved, and there's the subtle construction and destruction of the temporary QColor object as well.

But wait. If we know beforehand that all we want is to look up the value from the same table QT uses to look up it's named colors, wouldn't that be faster than doing all this?

So, my fellow intrepid Computer Mad Scientists, we have reinvented the wheel. And it's quite snappy.

Here's a test file set and an mc2.def file currently set to include debugging info. I like kdbg, for tracing and debugging but any cool graphical debugger, especially if it allows looking at the disassembly, should be fine.

file: src/main-01.cpp
purpose: test code
Code:
// main.cpp -- skeleton created by new.main

#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include "test-01.h"

void dbg(){} // for a non-moving breakpoint

#define PRINT_AND_DO(func) func; printf("Command: %s\n", #func)
#define newline() printf("\n")

int main(int , char** )
{
  dbg();
  // C++ file
  uint res; 
  res = PRINT_AND_DO(lqToRgb(Qt::magenta));
  printf("Result : 0xAARRGGBB = 0x%.8X\n", res);
  newline();  
  
  res = PRINT_AND_DO(lqToRgb(Qt::white));
  printf("Result : 0xAARRGGBB = 0x%.8X\n", res);
  newline();  

  res = PRINT_AND_DO(lqMixRgb(Qt::magenta, Qt::white, 0.0));
  printf("Result : 0xAARRGGBB = 0x%.8X\n", res);
  newline();  
  
  res = PRINT_AND_DO(lqMixRgb(Qt::magenta, Qt::white, .25));
  printf("Result : 0xAARRGGBB = 0x%.8X\n", res);
  newline();  
  
  res = PRINT_AND_DO(lqMixRgb(Qt::magenta, Qt::white, 0.5));
  printf("Result : 0xAARRGGBB = 0x%.8X\n", res);
  newline();  
  
  res = PRINT_AND_DO(lqMixRgb(Qt::magenta, Qt::white, .75));
  printf("Result : 0xAARRGGBB = 0x%.8X\n", res);
  newline();  

  res = PRINT_AND_DO(lqMixRgb(Qt::magenta, Qt::white, 1.0));
  printf("Result : 0xAARRGGBB = 0x%.8X\n", res);
  newline();  


  res = PRINT_AND_DO(lqToRgb(Qt::cyan));
  printf("Result : 0xAARRGGBB = 0x%.8X\n", res);
  newline();  
  
  res = PRINT_AND_DO(lqToRgb(Qt::red));
  printf("Result : 0xAARRGGBB = 0x%.8X\n", res);
  newline();  

  res = PRINT_AND_DO(lqMixRgb(Qt::cyan, Qt::red, 0.0));
  printf("Result : 0xAARRGGBB = 0x%.8X\n", res);
  newline();  
  
  res = PRINT_AND_DO(lqMixRgb(Qt::cyan, Qt::red, .25));
  printf("Result : 0xAARRGGBB = 0x%.8X\n", res);
  newline();  
  
  res = PRINT_AND_DO(lqMixRgb(Qt::cyan, Qt::red, 0.50));
  printf("Result : 0xAARRGGBB = 0x%.8X\n", res);
  newline();  
  
  res = PRINT_AND_DO(lqMixRgb(Qt::cyan, Qt::red, .75));
  printf("Result : 0xAARRGGBB = 0x%.8X\n", res);
  newline();  

  res = PRINT_AND_DO(lqMixRgb(Qt::cyan, Qt::red, 1.0));
  printf("Result : 0xAARRGGBB = 0x%.8X\n", res);
  newline();  
  
  return 0;
}

file: src/test-01.cpp
purpose: snippet from the next version of lq-qt/mc2
Code:
#include "test-01.h"

const uint lq_colortable[] = {
  0xFFFFFFFF, // Qt::color0
  0xFF000000, // Qt::color1
  0xFF000000, // black
  0xFFFFFFFF, // white
  0xFF808080, // index 248   medium gray
  0xFFA0A0A4, // index 247   light gray
  0xFFC0C0C0, // index 7     light gray
  0xFFFF0000, // index 249   red
  0xFF00FF00, // index 250   green
  0xFF0000FF, // index 252   blue
  0xFF00FFFF, // index 254   cyan
  0xFFFF00FF, // index 253   magenta
  0xFFFFFF00, // index 251   yellow
  0xFF800000, // index 1     dark red
  0xFF008000, // index 2     dark green
  0xFF000080, // index 4     dark blue
  0xFF008080, // index 6     dark cyan
  0xFF800080, // index 5     dark magenta
  0xFF808000, // index 3     dark yellow
  0x00000000  //             transparent
};


QRgb lqMixRgb(QRgb pixel1, QRgb pixel2, double frac, bool opaque /* true */)
{
  int a, b, c;
  uint res1, res2;
  
  // 8 bit fixed point multipliers
  b = 255 * frac;
  
  // these compare to 0 things where 0 and N are the possible returns 
  // run very fast and don't branch.  See the disassembly of the optimized code.
  b = (b < 0 ? 0 : b); 
  
  // Add and sub are faster than a branch too, so we use the compare to 0 
  // optimization to get the upper clamp value without branching.  Again,
  // see the disassembly.
  c = b - 255;
  c = (c > 0) ? 0 : c;
  b = c + 255;
  
  // fudge to compensate for limited fixed point precision
  a = 256 - b; 
  b++; // still testing fudge algorithm, possibly 'b = b > a ? b+1 : b;'
    
  // lo bits: mask, multiply, mask, shift
  res1 = (((pixel1 & 0x00FF00FF) * a) & 0xFF00FF00) >> 8;
  res1 += ((((pixel2 & 0x00FF00FF) * b) & 0xFF00FF00)) >> 8;
  
  // hi bits: mask, shift, multiply, mask
  res2 = (((pixel1 & 0xFF00FF00) >> 8) * a) & 0xFF00FF00; 
  res2 += (((pixel2 & 0xFF00FF00) >> 8) * b) & 0xFF00FF00; 
  
  return (res1 + res2) | ((-opaque) & 0xFF000000); // set alpha = 255 if opaque
}

file: src/test-01.h
purpose: header for the snippet
Code:
// test-01.h - template created with new.header

#ifndef test_01_h
#define test_01_h

#include <QColor>

// our copy of the table used by Qt::GlobalColor values such as Qt::gray
extern const uint lq_colortable[]; 

// returns an AARRGGBB pixel for a pixel for a Qt::<name> color
#define lqToRgb(x) lq_colortable[x]

QRgb lqMixRgb(QRgb pixel1, QRgb pixel2, double frac, bool opaque = true);

inline QRgb lqMixRgb(const Qt::GlobalColor &c1, const Qt::GlobalColor &c2, double frac, bool opaque = true)
{ return lqMixRgb(lqToRgb(c1), lqToRgb(c2), frac, opaque); }

inline QRgb lqMixRgb(const Qt::GlobalColor &c1, QRgb pixel2, double frac, bool opaque = true)
{ return lqMixRgb(lqToRgb(c1), pixel2, frac, opaque); }

inline QRgb lqMixRgb(QRgb pixel1, const Qt::GlobalColor &c2, double frac, bool opaque = true)
{ return lqMixRgb(pixel1, lqToRgb(c2), frac, opaque); }

#endif // test_01_h

You can create your own makefile any way you like but mc2 is easy. It currently installs in your home folder so you can create different users in order to mess with the code and NEVER accidentally link to the wrong libraries.

file: mc2.def
purpose: mc2 makefile templeate.
Code:
# mc2.def template created with Makefile Creator 'mc2'

# change main to *.so for a lib or plugin
OUTNAME = test-01

# Optionally include global or semi-global defs for your makefile here.
# include ../makeinclude # typical location for a PROJECT makeinclude
                         # if the current makefile is under a PROJECT

# my debug versions of all the QT libs are currently here
QT_DBGLIB = -L/opt/qt4/lib


# Note: the *DIR, COMPILE and LINK defs can't join lines due to the way 
# they are imported from the environment but user defined variables will 
# accept the backslash to join lines to create vertical lists.  See the 
# HDR and OBJ lists in the generated Makefile for example.

## The directories for sources, (temp) objects, and binary output(s)
## (single lines for BINDIR, SRCDIR, OBJDIR)
BINDIR = .
SRCDIR = src
OBJDIR = o

# example list of additional flags for a plugin. multi-line = ok.
DEFINES = \
  -DQT3_SUPPORT \

# other flags you can add
#  -DQT_SHARED \
#  -D_REENTRANT \
#  -DQT_PLUGIN \
#  -DQT_QT3SUPPORT_LIB \
#  -DQT_GUI_LIB \
#  -DQT_CORE_LIB \
#  ##############
#  -DQDESIGNER_EXPORT_WIDGETS  - for libs only
#  ##############



## What COMPILE should do. 
## (single lines for COMPILE, CFLAGS, INCLUDE)
COMPILE = g++ -c 

CFLAGS = $(DEFINES) -Wall -W -g3 # debuggable
#CFLAGS = $(DEFINES) -Wall -W -O2 # optimized
  # add -fPIC and -D_REENTRANT to the DEFINES list for shared libs and plugins
  # change -O2 to -g3 for debug versions
  
INCLUDE = -I$(SRCDIR) -I/usr/include -I/usr/include/Qt -I/usr/include/QtGui -I /usr/include/QtCore 

# add these as needed to the includes above.  
# -I /usr/include/Qt             -I /usr/include/QtHelp         -I /usr/include/QtSvg
# -I /usr/include/Qt3Support     -I /usr/include/QtMultimedia   -I /usr/include/QtTest
# -I /usr/include/QtCore         -I /usr/include/QtNetwork      -I /usr/include/QtUiTools
# -I /usr/include/QtDBus         -I /usr/include/QtOpenGL       -I /usr/include/QtWebKit
# -I /usr/include/QtDeclarative  -I /usr/include/QtScript       -I /usr/include/QtXml
# -I /usr/include/QtDesigner     -I /usr/include/QtScriptTools  -I /usr/include/QtXmlPatterns
# -I /usr/include/QtGui          -I /usr/include/QtSql

# If you have lots of them and want to join lines (i.e., '\') list them under 
# DEFINES similar to the way HDR or OBJ defs look in the Makefile and add $(DEFINES)
# to the CFLAGS


## What LINK should do. 
## (single lines for LINK, LDFLAGS, LIB)
LINK = g++ 
LDFLAGS = -lQtGui -lQtCore 

# add as needed. see notes for DEFINES and INCLUDES above if you need to split lines
# -lQt3Support              -lQtNetwork               -lQtCLucene
# -lQtOpenGL                -lQtCore                  -lQtScript
# -lQtDBus                  -lQtScriptTools           -lQtDeclarative
# -lQtSql                   -lQtDesignerComponents    -lQtSvg
# -lQtDesigner              -lQtTest                  -lQtGui 
# -lQtWebKit                -lQtHelp                  -lQtXmlPatterns
# -lQtMultimedia            -lQtXml


# adjust the LIB paths for your system.  Try 'locate libQtGui'
# to find the right path for your system.
LIB = -L/usr/lib 

################################################################
## User Defined Targets

clean: $(EXT_CLEAN) 
  @rm -f $(MAIN)
  @rm -f $(OBJ)
  @rm -f *~ */*~ */*/*~ */*/*/*~


semiclean: $(EXT_SEMICLEAN)
  @rm -f $(OBJ)
  @rm -f *~ */*~ */*/*~ */*/*/*~

# example
MOC_INCLUDE = -I $(SRCDIR)

# to create the ui_cmds.txt cd into SRCDIR and type
#   lq-uitool mocable > ui_cmds.txt
#   lq-uitool rccable > ui_cmds.txt
# See "lq-uitool commands" for more
ui: force
  cd $(SRCDIR) && sh ui_cmds.txt

clean-ui:
  cd $(SRCDIR) && rm -f moc_*.cpp qrc_*.cpp

init-ui: clean-ui clean
  cd $(SRCDIR) && lq-uitool qtcommands > ui_cmds.txt
  make ui --no-print-directory
  mc2 -update

targets:
  @echo "Default qt4 targets:"
  @echo "  all           - default (make all)"
  @echo "  clean         - remove temp object files and main file(s)"
  @echo "  semiclean:    - remove only temp object files and temp backups."
  @echo "  ui            - regenerate user interface related files (ui_* moc_* qrc_*)"
  @echo "  clean-ui      - remove user interface related files (ui_* moc_* qrc_*)"
  @echo "  init-ui       - uses lq-uitool to generate interface related files (see above)"


force: # used to force execution
Now let's see what the compiler does to get these colors with the -O2 switch.

I'll mark the lines where ebx is loaded with the pixel and where it's pushed onto the stack for printf().

Code:
res = PRINT_AND_DO(lqToRgb(Qt::white));
* 0x80485d0 mov    0x8048e0c,%ebx           // That was it. One opcode!
  0x80485d6 movl   $0x8048c28,0x4(%esp)     // the command we did (as a string)
  0x80485de movl   $0x8048bfd,(%esp)        // the fmt string
  0x80485e5 call   0x8048484 <printf@plt>   // and printf()
  
printf("Result : 0xAARRGGBB = 0x%.8X\n", res);
* 0x80485ea mov    %ebx,0x4(%esp)           // push onto the stack (still in ebx)
  0x80485ee movl   $0x8048c0a,(%esp)        // the fmt string
  0x80485f5 call   0x8048484 <printf@plt>   // and printf()
We are at near-optimum speed for color mixing now with almost no overhead for handling named Qt::<color> thingies.

See the inlined code that does the conversions depending on which parameters need decoding from the index numbers. C++ is good for this kind of thing because by way of name mangling, each of the routines actually has a different name though we don't usually see it.
Posted in Uncategorized
Views 516 Comments 0
« Prev     Main     Next »
Total Comments 0

Comments

 

  



All times are GMT -5. The time now is 09:02 AM.

Main Menu
Advertisement

My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
identi.ca: @linuxquestions
Facebook: linuxquestions Google+: linuxquestions
Open Source Consulting | Domain Registration