diff --git a/autoortho/aoconfig.py b/autoortho/aoconfig.py index 76c5f8ff..8ce5567f 100644 --- a/autoortho/aoconfig.py +++ b/autoortho/aoconfig.py @@ -101,6 +101,9 @@ class AOConfig(object): [windows] prefer_winfsp = False + +[coloring] +saturation = 100 """ def __init__(self, conf_file=None): diff --git a/autoortho/aoimage/AoImage.py b/autoortho/aoimage/AoImage.py index 3e3adc47..80ff7301 100755 --- a/autoortho/aoimage/AoImage.py +++ b/autoortho/aoimage/AoImage.py @@ -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): @@ -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) @@ -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}") diff --git a/autoortho/aoimage/aoimage.c b/autoortho/aoimage/aoimage.c index 7744e65b..2fcc4bc9 100755 --- a/autoortho/aoimage/aoimage.c +++ b/autoortho/aoimage/aoimage.c @@ -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); @@ -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; +} diff --git a/autoortho/aoimage/aoimage.dll b/autoortho/aoimage/aoimage.dll index 18f07b51..49cf88b6 100755 Binary files a/autoortho/aoimage/aoimage.dll and b/autoortho/aoimage/aoimage.dll differ diff --git a/autoortho/aoimage/aoimage.h b/autoortho/aoimage/aoimage.h index 64f8a763..1c4347ca 100755 --- a/autoortho/aoimage/aoimage.h +++ b/autoortho/aoimage/aoimage.h @@ -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 diff --git a/autoortho/config_ui.py b/autoortho/config_ui.py index 0029cade..443d1ff0 100644 --- a/autoortho/config_ui.py +++ b/autoortho/config_ui.py @@ -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, diff --git a/autoortho/getortho.py b/autoortho/getortho.py index 76072b42..8232a40b 100644 --- a/autoortho/getortho.py +++ b/autoortho/getortho.py @@ -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 @@ -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 #