Skip to content

Commit fb383d5

Browse files
committed
Add non-black brightness and contrast adjustment function and test example
1 parent 78bc653 commit fb383d5

File tree

4 files changed

+483
-0
lines changed

4 files changed

+483
-0
lines changed

IMGADJ/IMGADJ.BI

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,12 @@ DECLARE SUB GJ_IMGADJ_HSVtoRGB (hue AS SINGLE, sat AS SINGLE, value AS SINGLE, r
5656
DECLARE FUNCTION GJ_IMGADJ_CreateTestImage AS LONG
5757
DECLARE FUNCTION GJ_IMGADJ_CreateComplexTestImage AS LONG
5858
DECLARE FUNCTION GJ_IMGADJ_CreateGradientTestImage AS LONG
59+
DECLARE FUNCTION GJ_IMGADJ_CreateTestImageWithBlack AS LONG
5960
DECLARE FUNCTION GJ_IMGADJ_LoadTestImage (imageType AS STRING) AS LONG
6061
DECLARE SUB GJ_IMGADJ_ShowComparison (originalImg AS LONG, adjustedImg AS LONG, title AS STRING)
6162
DECLARE FUNCTION GJ_IMGADJ_Brightness (sourceImg AS LONG, direction AS STRING, amount AS INTEGER) AS LONG
6263
DECLARE FUNCTION GJ_IMGADJ_Contrast (sourceImg AS LONG, direction AS STRING, amount AS INTEGER) AS LONG
64+
DECLARE FUNCTION GJ_IMGADJ_BrightnessContrastNonBlack (sourceImg AS LONG, brightnessDir AS STRING, brightnessAmount AS INTEGER, contrastDir AS STRING, contrastAmount AS INTEGER) AS LONG
6365
DECLARE FUNCTION GJ_IMGADJ_Gamma (sourceImg AS LONG, direction AS STRING, amount AS INTEGER) AS LONG
6466
DECLARE FUNCTION GJ_IMGADJ_Saturation (sourceImg AS LONG, direction AS STRING, amount AS INTEGER) AS LONG
6567
DECLARE FUNCTION GJ_IMGADJ_Hue (sourceImg AS LONG, direction AS STRING, amount AS INTEGER) AS LONG

IMGADJ/IMGADJ.BM

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,101 @@ FUNCTION GJ_IMGADJ_Contrast (sourceImg AS LONG, direction AS STRING, amount AS I
275275
END FUNCTION
276276

277277

278+
''
279+
' Apply brightness and contrast adjustments to all non-black pixels
280+
' Black pixels (RGB 0,0,0) are preserved unchanged while all other pixels are adjusted
281+
' @param sourceImg LONG Source image handle
282+
' @param brightnessDir STRING "+" to increase brightness, "-" to decrease
283+
' @param brightnessAmount INTEGER Brightness adjustment amount (0-255)
284+
' @param contrastDir STRING "+" to increase contrast, "-" to decrease
285+
' @param contrastAmount INTEGER Contrast percentage (0-100)
286+
' @return LONG New image handle with adjustments applied to non-black pixels only
287+
' @example
288+
' adjusted = GJ_IMGADJ_BrightnessContrastNonBlack(myImage, "+", 30, "+", 20)
289+
' ' Increases brightness by 30 and contrast by 20% for all non-black pixels
290+
'
291+
FUNCTION GJ_IMGADJ_BrightnessContrastNonBlack (sourceImg AS LONG, brightnessDir AS STRING, brightnessAmount AS INTEGER, contrastDir AS STRING, contrastAmount AS INTEGER)
292+
IF sourceImg = 0 THEN
293+
GJ_IMGADJ_BrightnessContrastNonBlack = 0
294+
EXIT FUNCTION
295+
END IF
296+
297+
DIM resultImg AS LONG
298+
resultImg = _COPYIMAGE(sourceImg, 32)
299+
300+
' Calculate brightness offset
301+
DIM brightnessOffset AS INTEGER
302+
IF brightnessDir = "+" THEN
303+
brightnessOffset = brightnessAmount
304+
ELSE
305+
brightnessOffset = -brightnessAmount
306+
END IF
307+
308+
' Calculate contrast factor
309+
DIM contrastPct AS INTEGER
310+
IF contrastDir = "+" THEN
311+
contrastPct = contrastAmount
312+
ELSE
313+
contrastPct = -contrastAmount
314+
END IF
315+
316+
DIM contrastFactor AS DOUBLE
317+
contrastFactor = (259.0 * (contrastPct + 255.0)) / (255.0 * (259.0 - contrastPct))
318+
319+
' Apply adjustments using _MEMIMAGE for speed
320+
DIM m AS _MEM
321+
m = _MEMIMAGE(resultImg)
322+
323+
DIM w AS INTEGER, h AS INTEGER
324+
w = _WIDTH(resultImg)
325+
h = _HEIGHT(resultImg)
326+
327+
DIM x AS INTEGER, y AS INTEGER
328+
DIM pixelColor AS _UNSIGNED LONG
329+
DIM r AS INTEGER, g AS INTEGER, b AS INTEGER, a AS INTEGER
330+
DIM newR AS INTEGER, newG AS INTEGER, newB AS INTEGER
331+
332+
FOR y = 0 TO h - 1
333+
FOR x = 0 TO w - 1
334+
' Get pixel color using _MEMGET
335+
pixelColor = _MEMGET(m, m.OFFSET + (y * w + x) * 4, _UNSIGNED LONG)
336+
337+
' Extract RGBA components
338+
r = _RED32(pixelColor)
339+
g = _GREEN32(pixelColor)
340+
b = _BLUE32(pixelColor)
341+
a = _ALPHA32(pixelColor)
342+
343+
' Only adjust non-black pixels (skip if R=0, G=0, B=0)
344+
IF r <> 0 OR g <> 0 OR b <> 0 THEN
345+
' Apply brightness adjustment
346+
newR = r + brightnessOffset
347+
newG = g + brightnessOffset
348+
newB = b + brightnessOffset
349+
350+
' Apply contrast adjustment
351+
newR = INT(contrastFactor * (newR - 128) + 128)
352+
newG = INT(contrastFactor * (newG - 128) + 128)
353+
newB = INT(contrastFactor * (newB - 128) + 128)
354+
355+
' Clamp values to valid range
356+
newR = _CLAMP(newR, 0, 255)
357+
newG = _CLAMP(newG, 0, 255)
358+
newB = _CLAMP(newB, 0, 255)
359+
360+
' Create new pixel and store it using _MEMPUT
361+
pixelColor = _RGBA32(newR, newG, newB, a)
362+
_MEMPUT m, m.OFFSET + (y * w + x) * 4, pixelColor
363+
END IF
364+
' Black pixels (r=0, g=0, b=0) are left unchanged
365+
NEXT x
366+
NEXT y
367+
368+
_MEMFREE m
369+
GJ_IMGADJ_BrightnessContrastNonBlack = resultImg
370+
END FUNCTION
371+
372+
278373
''
279374
' Adjust image gamma
280375
' @param sourceImg LONG Source image handle
@@ -1577,3 +1672,140 @@ FUNCTION GJ_IMGADJ_CreateTestImage ()
15771672
_DEST oldDest
15781673
GJ_IMGADJ_CreateTestImage = img
15791674
END FUNCTION
1675+
1676+
1677+
''
1678+
' Apply pixel scaling algorithms to images
1679+
' Uses QB64PE's built-in pixel scalers for retro-style upscaling without blurring
1680+
'
1681+
' @param sourceImg LONG Source image handle
1682+
' @param scalerType INTEGER Type of pixel scaler to apply (use GJ_IMGADJ_PIXELSCALER_* constants)
1683+
' @return LONG New image handle with pixel scaling applied, 0 on error
1684+
' @example
1685+
' scaledImg = GJ_IMGADJ_PixelScaler(myImage, GJ_IMGADJ_PIXELSCALER_SXBR2)
1686+
' IF scaledImg > 0 THEN _PUTIMAGE (0, 0), scaledImg
1687+
' @version 1.0
1688+
' @author grymmjack
1689+
'
1690+
FUNCTION GJ_IMGADJ_PixelScaler (sourceImg AS LONG, scalerType AS INTEGER)
1691+
' Check if source image is valid
1692+
IF sourceImg = 0 OR sourceImg = -1 THEN
1693+
PRINT "GJ_IMGADJ_PixelScaler: Invalid source image handle"
1694+
GJ_IMGADJ_PixelScaler = 0
1695+
EXIT FUNCTION
1696+
END IF
1697+
1698+
' Validate scaler type
1699+
IF scalerType < 0 OR scalerType > 7 THEN
1700+
PRINT "GJ_IMGADJ_PixelScaler: Invalid scaler type (must be 0-7)"
1701+
GJ_IMGADJ_PixelScaler = 0
1702+
EXIT FUNCTION
1703+
END IF
1704+
1705+
' Map scaler type to requirements string
1706+
DIM requirements AS STRING
1707+
SELECT CASE scalerType
1708+
CASE GJ_IMGADJ_PIXELSCALER_SXBR2
1709+
requirements = "sxbr2"
1710+
CASE GJ_IMGADJ_PIXELSCALER_SXBR3
1711+
requirements = "sxbr3"
1712+
CASE GJ_IMGADJ_PIXELSCALER_SXBR4
1713+
requirements = "sxbr4"
1714+
CASE GJ_IMGADJ_PIXELSCALER_MMPX2
1715+
requirements = "mmpx2"
1716+
CASE GJ_IMGADJ_PIXELSCALER_HQ2XA
1717+
requirements = "hq2xa"
1718+
CASE GJ_IMGADJ_PIXELSCALER_HQ2XB
1719+
requirements = "hq2xb"
1720+
CASE GJ_IMGADJ_PIXELSCALER_HQ3XA
1721+
requirements = "hq3xa"
1722+
CASE GJ_IMGADJ_PIXELSCALER_HQ3XB
1723+
requirements = "hq3xb"
1724+
CASE ELSE
1725+
PRINT "GJ_IMGADJ_PixelScaler: Unknown scaler type"
1726+
GJ_IMGADJ_PixelScaler = 0
1727+
EXIT FUNCTION
1728+
END SELECT
1729+
1730+
' Simple approach: save to temp file, load with scaler, return result
1731+
DIM tempFileName AS STRING
1732+
tempFileName = "temp_scaler_" + _TRIM$(STR$(TIMER)) + ".png"
1733+
1734+
' Save source image to temporary file
1735+
_SAVEIMAGE tempFileName, sourceImg
1736+
1737+
' Check if the file was saved successfully
1738+
IF NOT _FILEEXISTS(tempFileName) THEN
1739+
PRINT "GJ_IMGADJ_PixelScaler: Failed to save temporary image file"
1740+
GJ_IMGADJ_PixelScaler = 0
1741+
EXIT FUNCTION
1742+
END IF
1743+
1744+
' Load directly from file with scaler - this is the simple working method
1745+
DIM scaledImg AS LONG
1746+
scaledImg = _LOADIMAGE(tempFileName, 32, requirements)
1747+
1748+
' Clean up temporary file
1749+
KILL tempFileName
1750+
1751+
' Check if loading was successful and return result
1752+
IF scaledImg = 0 OR scaledImg = -1 THEN
1753+
PRINT "GJ_IMGADJ_PixelScaler: Failed to load scaled image"
1754+
GJ_IMGADJ_PixelScaler = 0
1755+
EXIT FUNCTION
1756+
END IF
1757+
1758+
GJ_IMGADJ_PixelScaler = scaledImg
1759+
END FUNCTION
1760+
1761+
1762+
''
1763+
' Create a test image with distinct black areas and colored regions
1764+
' This helps demonstrate the difference between regular and non-black adjustments
1765+
' @return LONG Image handle with test pattern including black areas
1766+
' @example
1767+
' testImg = GJ_IMGADJ_CreateTestImageWithBlack()
1768+
'
1769+
FUNCTION GJ_IMGADJ_CreateTestImageWithBlack ()
1770+
DIM img AS LONG
1771+
DIM w AS INTEGER, h AS INTEGER
1772+
w = 300
1773+
h = 300
1774+
1775+
img = _NEWIMAGE(w, h, 32)
1776+
_DEST img
1777+
1778+
' Fill with a base color (dark blue)
1779+
CLS , _RGB32(40, 40, 100)
1780+
1781+
' Add some colored rectangles
1782+
LINE (50, 50)-(150, 150), _RGB32(200, 100, 50), BF ' Orange rectangle
1783+
LINE (200, 50)-(250, 150), _RGB32(50, 200, 100), BF ' Green rectangle
1784+
LINE (50, 200)-(150, 250), _RGB32(150, 50, 200), BF ' Purple rectangle
1785+
LINE (200, 200)-(250, 250), _RGB32(200, 200, 50), BF ' Yellow rectangle
1786+
1787+
' Add some black areas to test the non-black function
1788+
LINE (75, 75)-(125, 125), _RGB32(0, 0, 0), BF ' Black square in orange
1789+
LINE (175, 80)-(190, 120), _RGB32(0, 0, 0), BF ' Black bar
1790+
LINE (100, 175)-(140, 190), _RGB32(0, 0, 0), BF ' Black bar
1791+
LINE (225, 225)-(240, 240), _RGB32(0, 0, 0), BF ' Black square in yellow
1792+
1793+
' Add some circles with black centers
1794+
CIRCLE (100, 100), 20, _RGB32(255, 255, 255) ' White circle
1795+
PAINT (100, 100), _RGB32(0, 0, 0), _RGB32(255, 255, 255) ' Fill with black
1796+
1797+
CIRCLE (225, 100), 15, _RGB32(255, 0, 0) ' Red circle
1798+
PAINT (225, 100), _RGB32(0, 0, 0), _RGB32(255, 0, 0) ' Fill with black
1799+
1800+
' Add some text areas
1801+
COLOR _RGB32(255, 255, 255)
1802+
_PRINTSTRING (10, 10), "TEST IMAGE"
1803+
_PRINTSTRING (10, 280), "Black areas preserved"
1804+
1805+
' Add some pure black lines to test edge cases
1806+
LINE (0, 160)-(299, 160), _RGB32(0, 0, 0) ' Horizontal black line
1807+
LINE (160, 0)-(160, 299), _RGB32(0, 0, 0) ' Vertical black line
1808+
1809+
_DEST 0
1810+
GJ_IMGADJ_CreateTestImageWithBlack = img
1811+
END FUNCTION

IMGADJ/README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,34 @@ Image adjustment is a common need in graphics programming, but implementing effi
1414
- **Memory safety** - clear ownership of returned image handles
1515
- **Performance** - 10-200x faster than naive implementations
1616
- **Real-time capability** - suitable for interactive applications
17+
- **Selective processing** - NEW! Non-black adjustments preserve pure black pixels
1718

1819
The library draws inspiration from professional image editing software like Photoshop and GIMP, providing similar adjustment capabilities in a QB64PE-friendly format.
1920

21+
### **NEW!** Selective Black Preservation
22+
23+
The `GJ_IMGADJ_BrightnessContrastNonBlack&()` function addresses a common problem in image processing: maintaining pure black elements while adjusting the rest of the image.
24+
25+
**Problem Solved:**
26+
- Traditional brightness adjustments turn black (RGB 0,0,0) pixels gray
27+
- Black text, outlines, borders, and UI elements lose their crispness
28+
- Logos and graphics with black elements become muddy
29+
30+
**Solution Benefits:**
31+
- **Preserves text readability** - Black text stays perfectly black
32+
- **Maintains UI crispness** - Black borders and outlines remain sharp
33+
- **Artistic control** - Keep pure black shadows and artistic elements
34+
- **Logo processing** - Enhance colored portions without affecting black elements
35+
- **Efficient processing** - Single function combines brightness + contrast with pixel selection
36+
2037
## WHAT'S IN THE LIBRARY
2138

2239
### Core Adjustments
2340
| FUNCTION | PURPOSE | PARAMETERS | PERFORMANCE |
2441
|----------|---------|------------|-------------|
2542
| `GJ_IMGADJ_Brightness&()` | Adjust image brightness | `(img&, "+"/"-", amount%)` | 10-50x faster with _MEMIMAGE |
2643
| `GJ_IMGADJ_Contrast&()` | Adjust image contrast | `(img&, "+"/"-", percentage%)` | 10-50x faster with _MEMIMAGE |
44+
| `GJ_IMGADJ_BrightnessContrastNonBlack&()` | **NEW!** Adjust brightness/contrast while preserving black pixels | `(img&, brightDir$, brightAmt%, contrastDir$, contrastAmt%)` | Selective pixel processing |
2745
| `GJ_IMGADJ_Gamma&()` | Gamma correction | `(img&, "+"/"-", amount%)` | 100x faster with lookup tables |
2846
| `GJ_IMGADJ_Saturation&()` | Adjust color saturation | `(img&, "+"/"-", percentage%)` | HSV color space conversion |
2947
| `GJ_IMGADJ_Hue&()` | Shift hue around color wheel | `(img&, "+"/"-", degrees%)` | HSV color space conversion |
@@ -94,6 +112,7 @@ The library draws inspiration from professional image editing software like Phot
94112
| `GJ_IMGADJ_CreateComplexTestImage&()` | Generate test image | `()` | Complex pattern for testing |
95113
| `GJ_IMGADJ_CreateGradientTestImage&()` | Generate gradient | `()` | RGB gradient pattern |
96114
| `GJ_IMGADJ_CreateSimpleTestImage&()` | Generate simple image | `()` | Basic shapes and colors |
115+
| `GJ_IMGADJ_CreateTestImageWithBlack&()` | **NEW!** Generate test image with black areas | `()` | Specifically for testing non-black adjustments |
97116

98117
## USAGE
99118

@@ -149,6 +168,33 @@ DO
149168
LOOP
150169
```
151170

171+
#### Non-Black Brightness/Contrast (Selective Adjustment)
172+
```vb
173+
' Perfect for preserving black text, outlines, or UI elements
174+
DIM imageWithBlackElements AS LONG, selectively_adjusted AS LONG
175+
176+
' Load image with black text or graphics
177+
imageWithBlackElements = GJ_IMGADJ_CreateTestImageWithBlack&()
178+
179+
' Apply strong adjustments while preserving pure black pixels
180+
selectively_adjusted = GJ_IMGADJ_BrightnessContrastNonBlack&(imageWithBlackElements, "+", 60, "+", 40)
181+
182+
' Compare with regular adjustment (affects all pixels including black)
183+
DIM regular_adjusted AS LONG, temp AS LONG
184+
temp = GJ_IMGADJ_Brightness&(imageWithBlackElements, "+", 60)
185+
regular_adjusted = GJ_IMGADJ_Contrast&(temp, "+", 40)
186+
_FREEIMAGE temp
187+
188+
' Side-by-side comparison shows black preservation
189+
CALL GJ_IMGADJ_ShowComparison(regular_adjusted, selectively_adjusted, "Regular vs Non-Black Adjustment")
190+
191+
' Use cases for non-black adjustment:
192+
' • Photo editing with black text overlay
193+
' • UI graphics with black borders/outlines
194+
' • Logo enhancement while preserving black elements
195+
' • Artistic effects maintaining pure black shadows
196+
```
197+
152198
#### Batch Processing
153199
```vb
154200
DIM images(10) AS LONG, processed(10) AS LONG
@@ -332,6 +378,10 @@ _FREEIMAGE adjusted ' Always free when done!
332378
### Amount/Percentage Parameters
333379
- **Brightness**: 0-255 (amount to add/subtract)
334380
- **Contrast**: 0-100 (percentage change)
381+
- **BrightnessContrastNonBlack**:
382+
- brightDir/contrastDir: "+" or "-" (direction for each adjustment)
383+
- brightAmt: 0-255 (brightness amount), contrastAmt: 0-100 (contrast percentage)
384+
- Only affects pixels that are NOT pure black (RGB 0,0,0)
335385
- **Gamma**: 0-100 (gamma multiplier = 1.0 + amount/100)
336386
- **Saturation**: 0-200 (percentage change)
337387
- **Hue**: 0-360 (degrees to shift)

0 commit comments

Comments
 (0)