Skip to content

Commit

Permalink
Add support for desaturation
Browse files Browse the repository at this point in the history
  • Loading branch information
hotbso committed Mar 20, 2024
1 parent 1e81d5c commit cdbaf46
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 1 deletion.
3 changes: 3 additions & 0 deletions autoortho/aoconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ class AOConfig(object):
[windows]
prefer_winfsp = False
[coloring]
saturation = 100
"""

def __init__(self, conf_file=None):
Expand Down
21 changes: 21 additions & 0 deletions autoortho/aoimage/AoImage.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,27 @@ def paste(self, p_img, pos):
_aoi.aoimage_paste(self, p_img, pos[0], pos[1])
return True

def copy(self, height_only = 0):
new = AoImage()
if not _aoi.aoimage_copy(self, new, height_only):
log.error(f"AoImage.copy error: {self._errmsg.decode()}")
return None

return new

def crop(self, c_img, pos):
_aoi.aoimage_crop(self, c_img, pos[0], pos[1])
return True

def desaturate(self, saturation = 1.0):
assert 0.0 <= saturation and saturation <= 1.0
if saturation == 1.0 or saturation is None:
return self

if not _aoi.aoimage_desaturate(self, saturation):
log.error(f"AoImage.desaturate error: {self._errmsg.decode()}")
return None
return self

@property
def size(self):
Expand Down Expand Up @@ -163,8 +180,10 @@ def open(filename):
_aoi.aoimage_create.argtypes = (POINTER(AoImage), c_uint32, c_uint32, c_uint32, c_uint32, c_uint32)
_aoi.aoimage_tobytes.argtypes = (POINTER(AoImage), c_char_p)
_aoi.aoimage_from_memory.argtypes = (POINTER(AoImage), c_char_p, c_uint32)
_aoi.aoimage_copy.argtypes = (POINTER(AoImage), POINTER(AoImage), c_uint32)
_aoi.aoimage_paste.argtypes = (POINTER(AoImage), POINTER(AoImage), c_uint32, c_uint32)
_aoi.aoimage_crop.argtypes = (POINTER(AoImage), POINTER(AoImage), c_uint32, c_uint32)
_aoi.aoimage_desaturate.argtypes = (POINTER(AoImage), c_float)

def main():
logging.basicConfig(level = logging.DEBUG)
Expand Down Expand Up @@ -192,6 +211,8 @@ def main():
img = open("../testfiles/test_tile2.jpg")
log.info(f"AoImage.open {img}")

img.copy().desaturate(0.1).write_jpg("desaturated.jpg")

img2 = img.reduce_2()
log.info(f"img2: {img2}")

Expand Down
40 changes: 40 additions & 0 deletions autoortho/aoimage/aoimage.c
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,32 @@ AOIAPI void aoimage_tobytes(aoimage_t *img, uint8_t *data) {
memcpy(data, img->ptr, img->width * img->height * img->channels);
}

AOIAPI int32_t aoimage_copy(const aoimage_t *s_img, aoimage_t *d_img, uint32_t s_height_only) {
assert(NULL != s_img->ptr);
assert(s_height_only <= s_img->height);

if (0 == s_height_only)
s_height_only = s_img->height;

int dlen = s_img->width * s_height_only * s_img->channels;
uint8_t *dest = malloc(dlen);
if (NULL == dest) {
sprintf(d_img->errmsg, "can't malloc %d bytes", dlen);
d_img->ptr = NULL;
return FALSE;
}

memcpy(dest, s_img->ptr, dlen);
d_img->ptr = dest;
d_img->width = s_img->width;
d_img->height = s_height_only;
d_img->stride = 4 * d_img->width;
d_img->channels = 4;
d_img->errmsg[0] = '\0';
return TRUE;

}

AOIAPI int32_t aoimage_paste(aoimage_t *img, const aoimage_t *p_img, uint32_t x, uint32_t y) {
assert(x + p_img->width <= img->width);
assert(y + p_img->height <= img->height);
Expand Down Expand Up @@ -435,4 +461,18 @@ AOIAPI int32_t aoimage_crop(aoimage_t *img, const aoimage_t *c_img, uint32_t x,
return TRUE;
}

AOIAPI int32_t aoimage_desaturate(aoimage_t *img, float saturation) {
assert(img->channels == 4);

int len = img->width * img->height * 4;
for (uint8_t *ptr = img->ptr; ptr < img->ptr + len; ptr += 4) {
float luma = 0.212671 * ptr[0] + 0.715160 * ptr[1] + 0.072169 * ptr[2];
float x = (1.0 - saturation) * luma;
ptr[0] = (uint8_t)(saturation * ptr[0] + x);
ptr[1] = (uint8_t)(saturation * ptr[1] + x);
ptr[2] = (uint8_t)(saturation * ptr[2] + x);
}

return TRUE;
}

Binary file modified autoortho/aoimage/aoimage.dll
Binary file not shown.
4 changes: 4 additions & 0 deletions autoortho/aoimage/aoimage.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,12 @@ AOIAPI void aoimage_delete(aoimage_t *img);
AOIAPI int32_t aoimage_create(aoimage_t *img, uint32_t width, uint32_t height, uint32_t r, uint32_t g, uint32_t b);
AOIAPI int32_t aoimage_from_memory(aoimage_t *img, const uint8_t *data, uint32_t len);
AOIAPI void aoimage_tobytes(aoimage_t *img, uint8_t *data);
AOIAPI int32_t aoimage_copy(const aoimage_t *s_img, aoimage_t *d_img, uint32_t s_height_only);

// in place: img + pasted(p_img)
AOIAPI int32_t aoimage_paste(aoimage_t *img, const aoimage_t *p_img, uint32_t x, uint32_t y);
AOIAPI int32_t aoimage_crop(aoimage_t *img, const aoimage_t *c_img, uint32_t x, uint32_t y);

// in place desaturation
AOIAPI int32_t aoimage_desaturate(aoimage_t *img, float saturation);
#endif
11 changes: 11 additions & 0 deletions autoortho/config_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,17 @@ def ui_loop(self):
# metadata={'section':self.cfg.cache}
#),
],
[
sg.Text('Saturation'),
sg.Slider(
range=(0, 100, 5),
default_value = self.cfg.coloring.saturation,
key='saturation',
size=(20,15),
orientation='horizontal',
metadata={'section':self.cfg.coloring}
),
],
#[
# sg.Checkbox('Cleanup cache on start', key='clean_on_start',
# default=self.cfg.cache.clean_on_start,
Expand Down
14 changes: 13 additions & 1 deletion autoortho/getortho.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,8 @@ def __init__(self, col, row, maptype, zoom, min_zoom=0, priority=0,
dxt_format=CFG.pydds.format)
self.id = f"{row}_{col}_{maptype}_{zoom}"

# in % in the CFG
self.saturation = 0.01 * float(CFG.coloring.saturation)

def __lt__(self, other):
return self.priority < other.priority
Expand Down Expand Up @@ -649,8 +651,18 @@ def get_header(self):
self.ready.set()
return outfile

@locked
def get_img(self, mipmap, startrow=0, endrow=None, maxwait=5, min_zoom=None):
im = self._get_img(mipmap, startrow, endrow, maxwait, min_zoom)
if im is None:
return None

if self.saturation < 1.0:
im = im.copy().desaturate(self.saturation)

return im

@locked
def _get_img(self, mipmap, startrow=0, endrow=None, maxwait=5, min_zoom=None):
#
# Get an image for a particular mipmap
#
Expand Down

0 comments on commit cdbaf46

Please sign in to comment.