Color
ColorMask  Lab Report
Low-Order Bits in Color Image (Contrast Enhanced, False Color) [Linux Version]
. . .
Click here to see complete screen

Note:  The BMP file processed here was originally a JPEG image with a relatively low "quality" factor" that was also contrast enhanced.   See additional info below about such artifacts of JPEG compression process.


All  Bits in Same Color Image [Windows Version]

. . .
Click here to see complete screen (and the six rows of birds!)

Purpose
This purpose of this project is to display only the selected bits of a 24-bit color image.  Any or all of the 24-bits of each pixel can be masked for display in this project.


Background

A 24-bit color image is a matrix of pixels, usually with the (0,0) origin at the upper left corner.

A digital image is a matrix of pixels

Pixel(0,0) Pixel(1,0) Pixel(2,0) ...
Pixel(0,1) Pixel(1,1) Pixel(2,1) ...
Pixel(0,2) Pixel(1,2) Pixel(2,2) ...
... ... ... ...

Each 24-bit pixel consists of 8 bits of red, 8 bits of green and 8 bits of blue information. Each such RGB value can be represented in equivalent binary, hexadecimal or decimal forms.

A sample 24-bit pixel

Pixel Red Green Blue
Binary 1010 0001 1011 1010 1110 0011
Hexadecimal A1 BA E3
Decimal 161 186 227

For an 8-bit number, binary values range from 00000000 to 11111111, while hexadecimal values range from 00 to FF.  Decimal values range from 0 to 255.  Since each RGB color component is a number from 0 to 255, each pixel can have 256*256*256 = 16,777,216 possible RGB combinations.  [See the ColorMix and Maxwell Triangle Lab Reports for ideas about how color can be combined by the addition of primary colors.] 

A bit mask can be used to manipulate a bit string.  One way to extract bits from a bit field is to AND a given bit string with a specified "mask."  The "1" bits in the mask indicate the bit positions that are desired when using AND -- the "0" bits in the mask are automatically 0 in the result..  This is because 0 AND 1 = 0, while 1 AND 1 = 1.   (Or this can be interpreted as  FALSE AND TRUE = FALSE.  TRUE AND TRUE = TRUE). In summary, only "1" bits in the mask are selected using the AND operator.

The following table shows how to extract the least significant Red, least significant Green, and least significant Blue bit from a 24-bit pixel:

Obtaining the lowest-order RGB bits (1 bit R, 1 bit G, 1 bit B)

Red Green Blue
Pixel (binary) 1010 0001 1011 1010 1110 0011
Mask 0000 0001 0000 0001 0000 0001
Pixel AND Mask 0000 0001 0000 0000 0000 0001

Any desired bits from the original RGB pixel can be selected with the appropriate bit mask.  For example, to extract the two least significant Red bits, the four least significant Green bits, and the three least significant Blue bits, the following mask can be used:

Obtaining low-order RGB bits (2 bits R, 4 bits G, 3 bits B)

Red Green Blue
Pixel (binary) 1010 0001 1011 1010 1110 0011
Mask 0000 0011 0000 1111 0000 0111
Pixel AND Mask 0000 0001 0000 1010 0000 0011

We start with a 24-bit pixel where any bit position can be used, and there are 256 Red, 256 Green and 256 Blue values.  After masking, the number of possible RGB values is greatly reduced.  With two red bits there are 22 = 4 possible red values.  With four green bits there are 24 = 16 possible green values.  With three blue bits there are 23 = 8 possible blue values. Overall, with 2+4+3 bits, there are 22+4+3 = 4*16*8 = 29 = 512 possible RGB combinations.  In other words, there are only 512 possible colors with only 9 bits of RGB data.

Brightness Enhancement.  24-bit "True Color" is displayed when each of the RGB values can range from 0 to 255.  When fewer bits are used, especially when only looking at the low-order bits, the resulting image can be quite dark and does not display much contrast.  The masked bits can be shifted left to provide a brighter image.  One way to do this is to shift the mask by the number of bits it takes to shift the most significant bit of the mask to the far left in a byte, which is represented in the following example:

Brightness Enhancement Option 
(concept only, not implemented)

Red Green Blue
Pixel (binary) 1010 0001 1011 1010 1110 0011
Mask 0000 0011 0000 1111 0000 0111
Pixel AND Mask 0000 0001 0000 1010 0000 0011
Shifted Mask 1100 0000 1111 0000 1110 0000
Enhanced Brightness 0100 0000 1010 0000 0110 0000

Brightness/Contrast Enhancement.  Instead of just making the masked pixels a bit brighter for viewing, using the full 8-bit range will result in a high contrast image.  One way to do this is to record the actual min and max values for each of the masked RGB components (instead of using the possible min and max for the given bits).  The min to max range is then stretched to be from 0 to 255 (much like in the HistoStretch Grays Lab Report).  For example, the brightness/contrast enhanced green value would be computed as follows:

NewGreenValue  = 255 * (OldGreenValue - MinGreenValue) / (MaxGreenValue - MinGreen Value.

Applying such an enhancement separately to the red, green and blue channels will introduce a false-color, high contrast version of the masked data.  In this example, let's assume the 2-bit masked red data ranges from 0 to 3 (decimal), the 4-bit masked green data ranges from 1 to 10 (decimal) and the 3-bit masked blue data ranges from 1 to 6 (decimal).  The table below and the computations next explain how this brightness/contrast enhancement works:

NewRed   = 255 * ( 1 - 0) / ( 3 - 0) = 
NewGreen = 255 * (10 - 1) / (10 - 1) =
NewBlue  = 255 * ( 3 - 1) / ( 6 - 1) =

False Color Contrast Enhancement Option

Red Green Blue
Pixel AND Mask (binary) 0000 0001 0000 1010 0000 0011
Pixel AND Mask (decimal) 1 10 3
Contrast Enhanced (decimal) 85 255 102
Contrast Enhanced (binary) 0101 0101 1111 1111 0110 0110

Remember, this enhancement results in a false color image with maximum contrast.

Applications

Knowing which bits contribute to a displayed image might be useful in developing image compression schemes -- bits that are not noticeable to the human eye can be eliminated before compression begins. 

The resolution of digital cameras can be evaluated by displaying only the low-order bits of images.  Color bits being assigned by a digital camera should show non-random variation.  Bits not assigned by a digital camera will show random variation, i.e., they will just be "noise."

Inspection of bits in a bitmap can reveal image processing that has been applied to the image.  For example, low-quality factor JPEGs can readily be detected by looking at the low-order bits.

Steganography is a method of hiding information in an image.  The visual display of the lower order bits of an image might be useful in determining whether hidden information is stored in the image using steganography.  Random variation in what should be solid patterns might be an indication that hidden information is present in an image.


Materials and Equipment

Software Requirements
Windows 2000 (or later) and Delphi 7 
or
Linux and Kylix

ColorMasks executable

Hardware Requirements
1024-by-768 display in high or true color


Procedure

  1. Start the ColorMasks executable.  A default solid blue image is displayed.

  2. Press the Read button and select an image to view.  (In Windows you can load BMP, PNG, or ICO files.  In Linux you can additionally load JPEG files -- I cannot get JPEGs to work in Windows using Delphi CLX, but they seem to work OK using Kylix CLX.)

  3. Optional:  If the image is larger than the 640-by-480 viewing area, scroll bars will appear so all parts of the image can be viewed.  In addition to the scroll bars, press and drag the image to get to a desired region of interest that may not be visible.

  4. Optional:  Press the Stretch Image check box to force the image, no matter how small or large, to fit in the fixed-size display area.

  5. Experiment with displaying only certain bits of the 24-bit color image.  Check any of the Red, Green or Blue Mask checkboxes.  Notice the Combination column of numbers shows the number of possible Red, Green and Blue values.  The product of these Red, Green and Blue combinations gives the total number of possible color combinations in an image.  However, unless an image is quite large, there may be more possible pixel color combinations than then the number of pixels in an images.  The actual number of distinct RGB triples in the image is displayed under the number of possible combinations of RGB values. 

  6. Move the mouse cursor over the image to see the original RGB values and the result of the selected color mask at the lower left.

  7. Select the Enhance Contrast checkbox to see information in the lower-order bits.  False colors are displayed when Enhance Contrast is selected, but the information content of the bits can be visualized.

  8. Press the All On or All Off buttons to select all or none of the color mask checkboxes.  To see the least significant RGB values (like used in the example at the top of the page), press All Off and then select the right-most color mask checkboxes -- under the value "1".  To see the most significant RGB values, press All Off and then select the left-most color mask checkboxes.  Notice the All Off button automatically selects Enhance Contrast checkbox.

  9. Optional:  To save a masked bitmap, press the Write button and save as a BMP or PNG image in Windows.  In Linux, JPG images can also be written.


Discussion

Pressing the Read button results in the ButtonReadClick method being called.   The routine LoadGraphicsFile is called to use a TPicture to load any of the registered image types.  Unfortunately, Borland has not made this as easy in Delphi/Kylix CLX as it was in Delphi VCL.  Borland has been a bit myopic in only supporting BMP files well (and should have supported other files types such as GIF or TIFF natively long ago). 

The following function works fine in Kylix CLX and Delphi VCL, but fails using Delphi CLX to read a JPEG image.  I have seen various suggestions in various UseNet posts, but I cannot seem to get them to work in reading JPEG images in Delphi CLX.

LoadGraphicsFile

// Create TBitmap from any format supported by TGraphic.
FUNCTION LoadGraphicsFile(CONST Filename: STRING): TBitmap;
  VAR
    Picture:  TPicture;
BEGIN
  Result := NIL;
  IF   FileExists(Filename)
  THEN BEGIN
    Result := TBitmap.Create;
    TRY
      Picture := TPicture.Create;
      TRY
        Picture.LoadFromFile(Filename);
        // Try converting image to bitmap
        TRY
          Result.Assign(Picture.Graphic);
        EXCEPT
          // Picture didn't support conversion to TBitmap.
          // Try drawing image on bitmap instead.
          Result.Width  := Picture.Graphic.Width;
          Result.Height := Picture.Graphic.Height;
          Result.PixelFormat := pf32bit;
          Result.Canvas.Draw(0, 0, Picture.Graphic);
        END;
      FINALLY
        Picture.Free;
      END;
    EXCEPT
      Result.Free;
      RAISE;
    END
  END
END {LoadGraphicsFile};

When an image is loaded, or after any change in the various check box selections, the MaskAndDisplayBitmap method selects only the desired bits in each pixel as a new bitmap is created and displayed.

Note how the AdjustColor function is used to display enhanced contrast using false colors when the Enhance Contrast checkbox is selected.

PROCEDURE TFormColorMasks.MaskAndDisplayBitmap;
  VAR
    CountBlue :  BYTE;
    CountGreen:  BYTE;
    CountRed  :  BYTE;
    i         :  INTEGER;
    j         :  INTEGER;
    MaxB      :  BYTE;
    MaxG      :  BYTE;
    MaxR      :  BYTE;
    MinB      :  BYTE;
    MinG      :  BYTE;
    MinR      :  BYTE;
    RGBCount  :  INTEGER;
    RowIn     :  pRGBQuadArray;
    RowOut    :  pRGBQuadArray;
  // Brute force approach
  PROCEDURE CalcStats(CONST M128, M64, M32, M16, M8, M4, M2, M1: Boolean;
                      VAR Mask:  BYTE; VAR Count:  BYTE);
  BEGIN
    Count := 0;
    Mask := 0;
    IF   M128
    THEN BEGIN
      Mask := Mask + 128;
      INC(Count)
    END;
    IF   M64
    THEN BEGIN
      Mask := Mask +  64;
      INC(Count)
    END;
    IF   M32
    THEN BEGIN
      Mask := Mask +  32;
      INC(Count)
    END;
    IF   M16
    THEN BEGIN
      Mask := Mask +  16;
      INC(Count)
    END;
    IF   M8
    THEN BEGIN
      Mask := Mask +   8;
      INC(Count)
    END;
    IF   M4
    THEN BEGIN
      Mask := Mask +   4;
      INC(Count);
    END;
    IF   M2
    THEN BEGIN
      Mask := Mask +   2;
      INC(Count);
    END;
    IF   M1
    THEN BEGIN
      Mask := Mask +   1;
      INC(Count)
    END
  END {CalcStats};
  FUNCTION AdjustColor(CONST value:  BYTE; minValue, maxValue:  BYTE):  BYTE;
  BEGIN
    RESULT := value;
    IF   value > 0
    THEN BEGIN
      IF   minValue = maxValue
      THEN RESULT := 255
      ELSE RESULT := 255 * (Integer(value)    - Integer(minValue)) DIV
                           (Integer(maxValue) - Integer(minValue));
    END
  END {AdjustColor};
BEGIN
  Screen.Cursor := crHourGlass;
  TRY
    CalcStats(CheckBoxR128.Checked, CheckBoxR64.Checked,
              CheckBoxR32.Checked,  CheckBoxR16.Checked,
              CheckBoxR8.Checked,   CheckBoxR4.Checked,
              CheckBoxR2.Checked,   CheckBoxR1.Checked, MaskRed, CountRed);
    CalcStats(CheckBoxG128.Checked, CheckBoxG64.Checked,
              CheckBoxG32.Checked,  CheckBoxG16.Checked,
              CheckBoxG8.Checked,   CheckBoxG4.Checked,
              CheckBoxG2.Checked,   CheckBoxG1.Checked, MaskGreen, CountGreen);
    CalcStats(CheckBoxB128.Checked, CheckBoxB64.Checked,
              CheckBoxB32.Checked,  CheckBoxB16.Checked,
              CheckBoxB8.Checked,   CheckBoxB4.Checked,
              CheckBoxB2.Checked,   CheckBoxB1.Checked, MaskBlue, CountBlue);
    LabelDecR.Caption := IntToStr(MaskRed);
    LabelDecG.Caption := IntToStr(MaskGreen);
    LabelDecB.Caption := IntToStr(MaskBlue);
    LabelHexR.Caption := IntToHex(MaskRed,  2);
    LabelHexG.Caption := IntToHex(MaskGreen,2);
    LabelHexB.Caption := IntToHex(MaskBlue, 2);
    LabelComboR.Caption := IntToStr(Round(IntPower(2, CountRed)));
    LabelComboG.Caption := IntToStr(Round(IntPower(2, CountGreen)));
    LabelComboB.Caption := IntToStr(Round(IntPower(2, CountBlue)));
    LabelComboRGB.Caption := FormatFloat(',#',
                                IntPower(2, CountRed)  *
                                IntPower(2, CountGreen)*
                                IntPower(2, CountBlue)  );
    IF   Assigned(MaskedBitmap)
    THEN MaskedBitmap.Free;
    MaskedBitmap := TBitmap.Create;
    MaskedBitmap.PixelFormat := pf32bit;
    MaskedBitmap.Width  := BaseBitmap.Width;
    MaskedBitmap.Height := BaseBitmap.Height;
    minR := 255;
    minG := 255;
    minB := 255;
    maxR := 0;
    maxG := 0;
    maxB := 0;
    FOR j := 0 TO BaseBitmap.Height - 1 DO
    BEGIN
      RowIn  := BaseBitmap.ScanLine[j];
      RowOut := MaskedBitmap.Scanline[j];
      FOR i := 0 TO BaseBitmap.Width - 1 DO
      BEGIN
        // apply each color mask
        RowOut[i].rgbRed   := RowIn[i].rgbRed   AND MaskRed;
        RowOut[i].rgbGreen := RowIn[i].rgbGreen AND MaskGreen;
        RowOut[i].rgbBlue  := RowIn[i].rgbBlue  AND MaskBlue;
        IF   CheckBoxContrast.Checked
        THEN BEGIN
          // keep min / max info before applying mask
          IF   RowOut[i].rgbRed > maxR
          THEN maxR := RowOut[i].rgbRed
          ELSE BEGIN
            IF   RowOut[i].rgbRed < minR
            THEN minR := RowOut[i].rgbRed;
          END;
          IF   RowOut[i].rgbGreen > maxG
          THEN maxG := RowOut[i].rgbGreen
          ELSE BEGIN
            IF   RowOut[i].rgbGreen < minG
            THEN minG := RowOut[i].rgbGreen;
          END;
          IF   RowOut[i].rgbBlue > maxB
          THEN maxB := RowOut[i].rgbBlue
          ELSE BEGIN
            IF   RowOut[i].rgbBlue < minB
            THEN minB := RowOut[i].rgbBlue;
          END;
        END;
      END
    END;
    RGBCount := CountColors(MaskedBitmap);
    LabelRGBTriples.Caption := 'RGB Triples = ' + FormatFloat(',#', RGBCount);
    IF   CheckBoxContrast.Checked
    THEN BEGIN
      FOR j := 0 TO MaskedBitmap.Height - 1 DO
      BEGIN
        RowIn  := BaseBitmap.ScanLine[j];
        RowOut := MaskedBitmap.Scanline[j];
        FOR i := 0 TO MaskedBitmap.Width - 1 DO
        BEGIN
          RowOut[i].rgbRed   := AdjustColor(RowIn[i].rgbRed   AND MaskRed,   
                                            minR, maxR);
          RowOut[i].rgbGreen := AdjustColor(RowIn[i].rgbGreen AND MaskGreen, 
                                            minG, maxG);
          RowOut[i].rgbBlue  := AdjustColor(RowIn[i].rgbBlue  AND MaskBlue,  
                                            minB, maxB);
        END
      END
    END;
    Image.Picture.Graphic := MaskedBitmap
  FINALLY
    Screen.Cursor := crDefault
  END
END {MaskAndDisplayBitmap};

The actual number of unique RGB triples in an image is computed using the CountColors routine.  This routine creates a (possibly sparse) 256-by-256-by-256 bit array.  This is done with a 256-by-256 array of TBits in the Flags variable.  Each pixel in the image is reviewed, and the corresponding RGB bit is set.  After processing all the pixels in the image, the set bits are counted to find the number of unique RGB combinations.

TYPE
  TRGBQUAD =        // pf32pixel
  PACKED RECORD
    rgbBlue : BYTE;
    rgbGreen: BYTE;
    rgbRed  : BYTE;
    rgbReserved: Byte;
  END;
  TRGBQuadArray = ARRAY[WORD] OF TRGBQuad;
  pRGBQuadArray = ^TRGBQuadArray;                   
//==  CountColors  =====================================================
// Count number of unique R-G-B triples in a pf32bit Bitmap.
//
// Use 2D array of TBits objects -- when (R,G) combination occurs
// for the first time, create 256-bit array of bits in blue dimension.
// So, overall this is a fairly sparse matrix for most pictures.
// Tested with pictures created with a known number of colors, including
// a specially constructed image with 1024*1024 = 1,048,576 colors.
//
// efg, October 1998.
// Converted from pf24bit to pf32bit for Kylix, Sept 2002.
FUNCTION CountColors(CONST Bitmap:  TBitmap):  INTEGER;
  VAR
    Flags:  ARRAY[BYTE, BYTE] OF TBits;
    i    :  INTEGER;
    j    :  INTEGER;
    k    :  INTEGER;
    rowIn:  pRGBQuadArray;
BEGIN
  // Be sure bitmap is 32-bits/pixel
  ASSERT (Bitmap.PixelFormat = pf32Bit);
  // Clear 2D array of TBits objects
  FOR j := 0 TO 255 DO
    FOR i := 0 TO 255 DO
      Flags[i,j] := NIL;
  // Step through each scanline of image
  FOR j := 0 TO Bitmap.Height-1 DO
  BEGIN
    rowIn  := Bitmap.Scanline[j];
    FOR i := 0 TO Bitmap.Width-1 DO
    BEGIN
      WITH rowIn[i] DO
      BEGIN
        IF   NOT Assigned(Flags[rgbRed, rgbGreen])
        THEN BEGIN
          // Create 3D column when needed
          Flags[rgbRed, rgbGreen] := TBits.Create;
          Flags[rgbRed, rgbGreen].Size := 256;
        END;
        // Mark this R-G-B triple
        Flags[rgbRed,rgbGreen].Bits[rgbBlue] := TRUE
      END
    END
  END;
  RESULT := 0;
  // Count and Free TBits objects
  FOR j := 0 TO 255 DO
  BEGIN
    FOR i := 0 TO 255 DO
    BEGIN
      IF   Assigned(Flags[i,j])
      THEN BEGIN
        FOR k := 0 TO 255 DO
          IF   Flags[i,j].Bits[k]
          THEN INC(RESULT);
        Flags[i,j].Free;
      END
    END
  END
END {CountColors};

As mentioned above, I am not particularly happy with the way Borland has provided support for various graphics file formats in Delphi VCL, or Delphi/Kylix CLX.  Without the web pages written by Chris Rorden and UseNet post by Eric Sibert, I wouldn't have known how to write a CLX TBitmap back to disk in a format other than BMP.  

The ButtonWriteClick method creates BMP, PNG or JPG output files.

// See:  PNG Graphics with Delphi and Kylix
// www.psychology.nottingham.ac.uk/staff/cr1/png.html 
// and suggestion by Eric Sibert in 14 Nov 2001 posting
// to borland.public.delphi.graphics
procedure TFormColorMasks.ButtonWriteClick(Sender: TObject);
  var
   lWideStr:  WideString;
begin
{$IFDEF MSWINDOWS}
  SaveDialog.Filter := 
    // Restrict to list of filetypes that really work
    'All(*.bmp;*.png)|*.bmp;*.png|' +
    'BMP file (*.bmp)|*.bmp|' +
    'PNG file (*.png)|*.png';
{$ENDIF}
{$IFDEF LINUX}
 SaveDialog.Filter := 
    // Restrict to list of filetypes that really work
    'All(*.jpg;*.bmp;*.png)|*.jpg;*.bmp;*.png|' +
    'JPG file (*.jpg)|*.jpg|' +
    'BMP file (*.bmp)|*.bmp|' +
    'PNG file (*.png)|*.png';
{$ENDIF}
  IF   SaveDialog.Execute
  THEN BEGIN
    IF   UpperCase(ExtractFileExt(SaveDialog.FileName)) = '.PNG'
    THEN BEGIN
      lWideStr := SaveDialog.Filename;
      QPixMap_save (MaskedBitmap.Handle, @lWideStr, pChar('PNG'));
    END;
    IF   UpperCase(ExtractFileExt(SaveDialog.FileName)) = '.JPG'
    THEN BEGIN
      lWideStr := SaveDialog.Filename;
      QPixMap_save(MaskedBitmap.Handle, @lWideStr, pChar('JPEG'), 80 {quality});
    END;
   IF   UpperCase(ExtractFileExt(SaveDialog.FileName)) = '.BMP'
   THEN MaskedBitmap.SaveToFile(SaveDialog.Filename)
  END
end;

Image Scrolling Using TScrollbars

One way to scroll an image is to place a TBitmap in a TImage on a TPanel and then adjust the top left corner of the TImage relative to the TPanel.  The Top and Left properties of the TImage can negative when the TImage is above or to the left of the TPanel.

ButtonReadClickProcessing calls the UpdateBitmapSetup method when a new image is loaded into memory.  If CheckBoxStretch is checked, the scrollbars are not visible since they are not needed.  The scrollbars are also not visible when the loaded image is smaller than the TPanel.  Otherwise, UpdateBitmapSetup sets the maximum values for the horizontal and vertical scrollbars:

ScrollBarHorizontal.Max := Max(Image.Width  - PanelImage.Width,  0);
ScrollBarVertical.Max   := Max(Image.Height - PanelImage.Height, 0);

Moving the horizontal and/or vertical scrollbars allows viewing all parts of the TImage in a window the size of the TPanel.  

Image Scrolling By Dragging the Image

Sometimes dragging the image is easier to find a region of interest instead of using the TScrollbars.  This can be done via MouseDown, MouseMove and MouseUp events of the TImage.

The ImageMouseDown event records the location of the start of the dragging, which is used in ImageMouseMove to move the image by the amount of the drag and update the position of the TScrollbars.  While not completely necessary, some extra logic is used to make sure the image is not dragged beyond it natural corners.

procedure TFormColorMasks.ImageMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  Dragging := TRUE;
  // Dragging start point
  StartPressPoint  := Point(X,Y);
  // Location of image withing panel
  StartBitmapPoint := Point(Image.Left, Image.Top)
end;
procedure TFormColorMasks.ImageMouseMove(Sender: TObject;
  Shift: TShiftState; X, Y: Integer);
  VAR
    RGB    :  TRGBQuad;
    i      :  INTEGER;
    j      :  INTEGER;
    xDelta :  INTEGER;
    yDelta :  INTEGER;
begin
  IF   Dragging
  THEN BEGIN
    // If image is smaller than panel and scroll bars are not present,
    // don't bother dragging image around.
    IF   ScrollBarHorizontal.Visible
    THEN BEGIN
      xDelta := X - StartPressPoint.X;
      // Adjust location of upper left corner of image from
      // dragging image.
      Image.Left :=
        Max(-Image.Width + PanelImage.Width,
          Min(0,
            StartBitmapPoint.X + xDelta));
      // Update scrollbar
      ScrollBarHorizontal.Position := -Image.Left;
      // Because Image.Left point changed, the location of the
      // image within the panel must also be updated.
      StartBitmapPoint.X := Image.Left
    END;
    IF   ScrollBarVertical.Visible
    THEN BEGIN
      yDelta := Y - StartPressPoint.Y;
      Image.Top  :=
        Max(-Image.Height + PanelImage.Height,
          Min(0,
            StartBitmapPoint.Y + yDelta));
      ScrollBarVertical.Position   := -Image.Top;
      StartBitmapPoint.Y := Image.Top
    END;
    UpdateCorners
  END;
  ...
end;
procedure TFormColorMasks.ImageMouseUp(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  Dragging := FALSE
end;

 See the self-contained Image Scrolling example.


JPEG Compression

The picture below of two parrots has appeared in many image processing articles.  This particular JPEG image has a quality factor of 80.  (The BMPJPG Lab Report shows a graph of how image file size (and indirectly image fidelity) is affected by this quality factor.)

JPEG Image with quality factor = 80

The low order RGB bits of this image with quality factor of 80 show considerable variation: 

When the JPEG quality factor is reduced to 24, many areas of the image have lost fideilty:

When the JPEG image quality is reduced to 1 (the lowest possible value), information has been lost, but the picture is almost recognizable:

The low-order bits shown above in false color are artifacts from image compression using the discrete cosine tranform in JPEGs.  For other info about JPEGs see this MIT Powerpoint presentation for an explanation of JPEG compression.


Steganography 

Several years ago a German company in marketing their Steganos Security Suite showed the following images on their web site.  I remember wondering if they were really telling the truth about these images, since they appeared to be identical.

BEFORE
This image does not 
contain hidden data.

AFTER
This image contains a hidden 
and encrypted text file.

How can we "see" that the "After" image really has information not contained in the "Before" image?  Display of the least-significant bits of the before and after images shows that "information" is spread as "noise" in the "after" image:

Low order Red, Green and Blue Bits (3 bits)

So, "noise" in the least significant bits of an image could indicate limited resolution of a digital source -- or, a hidden message!


Conclusions
A color mask may be useful in determining the information content of specific bits in a 24-bit color image.


Keywords
Bit mask, color mask, true color, Delphi CLX, Kylix CLX, TPicture, BMP, JPG, PNG, Scanline, TRGBQuad, TRGBQuadArray, Contrast enhancement, CountColors, image scrolling

Files

        CLX (Component Library for Cross-Platform -- Windows or Linux)


Updated 26 Feb 2005


since 25 Feb 2003