diff --git a/scikits/image/io/_plugins/_colormixer.pyx b/scikits/image/io/_plugins/_colormixer.pyx index ee79bb56..f229f4ea 100644 --- a/scikits/image/io/_plugins/_colormixer.pyx +++ b/scikits/image/io/_plugins/_colormixer.pyx @@ -125,3 +125,308 @@ def brightness(np.ndarray[np.uint8_t, ndim=3] img, img[i, j, k] = 0 else: img[i, j, k] = op_result + +cdef void rgb_2_hsv(float* RGB, float* HSV): + '''Convert an HSV value to RGB. + + Automatic clipping. + + Parameters + ---------- + R : float + From 0. - 255. + G : float + From 0. - 255. + B : float + From 0. - 255. + + Returns + ------- + out : (H, S, V) Floats + Ranges (0...360), (0...1), (0...1) + + conversion convention from here: + http://en.wikipedia.org/wiki/HSL_and_HSV + + ''' + + cdef float R, G, B, H, S, V, MAX, MIN + R = RGB[0] + G = RGB[1] + B = RGB[2] + + if R > 255: + R = 255 + elif R < 0: + R = 0 + else: + pass + + if G > 255: + G = 255 + elif G < 0: + G = 0 + else: + pass + + if B > 255: + B = 255 + elif B < 0: + B = 0 + else: + pass + + if R < G: + MIN = R + MAX = G + else: + MIN = G + MAX = R + + if B < MIN: + MIN = B + elif B > MAX: + MAX = B + else: + pass + + V = MAX / 255. + + if MAX == MIN: + H = 0. + elif MAX == R: + H = (60 * (G - B) / (MAX - MIN)) % 360 + elif MAX == G: + H = 60 * (B - R) / (MAX - MIN) + 120 + else: + H = 60 * (R - G) / (MAX - MIN) + 240 + + if MAX == 0: + S = 0 + else: + S = 1 - MIN / MAX + + HSV[0] = H + HSV[1] = S + HSV[2] = V + +cdef void hsv_2_rgb(float* HSV, float* RGB): + '''Convert an HSV value to RGB. + + Automatic clipping. + + Parameters + ---------- + H : float + From 0. - 360. + S : float + From 0. - 1. + V : float + From 0. - 1. + + Returns + ------- + out : (R, G, B) Floats + Each from 0. - 1. + + conversion convention from here: + http://en.wikipedia.org/wiki/HSL_and_HSV + + ''' + + cdef float H, S, V + cdef float f, p, q, t, r, g, b + cdef int hi + + H = HSV[0] + S = HSV[1] + V = HSV[2] + + if H > 360: + H = 360 + elif H < 0: + H = 0 + else: + pass + + if S > 1: + S = 1 + elif S < 0: + S = 0 + else: + pass + + if V > 1: + V = 1 + elif V < 0: + V = 0 + else: + pass + + hi = ((H / 60.)) % 6 + f = (H / 60.) - ((H / 60.)) + p = V * (1 - S) + q = V * (1 - f * S) + t = V * (1 - (1 -f) * S) + + if hi == 0: + r = V + g = t + b = p + elif hi == 1: + r = q + g = V + b = p + elif hi == 2: + r = p + g = V + b = t + elif hi == 3: + r = p + g = q + b = V + elif hi == 4: + r = t + g = p + b = V + else: + r = V + g = p + b = q + + RGB[0] = r + RGB[1] = g + RGB[2] = b + +@cython.boundscheck(False) +def hsv_add(np.ndarray[np.uint8_t, ndim=3] img, + np.ndarray[np.uint8_t, ndim=3] stateimg, + float h_amt, float s_amt, float v_amt): + """Modify the image color by specifying additive HSV Values. + + Since the underlying images are RGB, all three values HSV + must be specified at the same time. + + The RGB triplet in the image is converted to HSV, the operation + is applied, and then the HSV triplet is converted back to RGB + + HSV values are scaled to H(0. - 360.), S(0. - 1.), V(0. - 1.) + then the operation is performed and any overflow is clipped, then the + reverse transform is performed. Those are the ranges to keep in mind, + when passing in values. + + Parameters + ---------- + img : (M, N, 3) ndarray of uint8 + Output image. + stateimg : (M, N, 3) ndarray of uint8 + Input image. + h_amt : float + Ammount to add to H channel. + s_amt : float + Ammount to add to S channel. + v_amt : float + Ammount to add to V channel. + + + """ + + cdef int height = img.shape[0] + cdef int width = img.shape[1] + + cdef float HSV[3] + cdef float RGB[3] + + cdef int i, j + + for i in range(height): + for j in range(width): + RGB[0] = stateimg[i, j, 0] + RGB[1] = stateimg[i, j, 1] + RGB[2] = stateimg[i, j, 2] + + rgb_2_hsv(RGB, HSV) + + # Add operation + HSV[0] += h_amt + HSV[1] += s_amt + HSV[2] += v_amt + + hsv_2_rgb(HSV, RGB) + + RGB[0] *= 255 + RGB[1] *= 255 + RGB[2] *= 255 + + img[i, j, 0] = RGB[0] + img[i, j, 1] = RGB[1] + img[i, j, 2] = RGB[2] + +@cython.boundscheck(False) +def hsv_multiply(np.ndarray[np.uint8_t, ndim=3] img, + np.ndarray[np.uint8_t, ndim=3] stateimg, + float h_amt, float s_amt, float v_amt): + """Modify the image color by specifying multiplicative HSV Values. + + Since the underlying images are RGB, all three values HSV + must be specified at the same time. + + The RGB triplet in the image is converted to HSV, the operation + is applied, and then the HSV triplet is converted back to RGB + + HSV values are scaled to H(0. - 360.), S(0. - 1.), V(0. - 1.) + then the operation is performed and any overflow is clipped, then the + reverse transform is performed. Those are the ranges to keep in mind, + when passing in values. + + Parameters + ---------- + img : (M, N, 3) ndarray of uint8 + Output image. + stateimg : (M, N, 3) ndarray of uint8 + Input image. + h_amt : float + Ammount to add to H channel. + s_amt : float + Ammount to add to S channel. + v_amt : float + Ammount to add to V channel. + + + """ + + cdef int height = img.shape[0] + cdef int width = img.shape[1] + + cdef float HSV[3] + cdef float RGB[3] + + cdef int i, j + + for i in range(height): + for j in range(width): + RGB[0] = stateimg[i, j, 0] + RGB[1] = stateimg[i, j, 1] + RGB[2] = stateimg[i, j, 2] + + rgb_2_hsv(RGB, HSV) + + # Multiply operation + HSV[0] *= h_amt + HSV[1] *= s_amt + HSV[2] *= v_amt + + hsv_2_rgb(HSV, RGB) + + RGB[0] *= 255 + RGB[1] *= 255 + RGB[2] *= 255 + + img[i, j, 0] = RGB[0] + img[i, j, 1] = RGB[1] + img[i, j, 2] = RGB[2] + + + + + diff --git a/scikits/image/io/_plugins/qt_plugin.py b/scikits/image/io/_plugins/qt_plugin.py index d2d6c757..bd690d89 100644 --- a/scikits/image/io/_plugins/qt_plugin.py +++ b/scikits/image/io/_plugins/qt_plugin.py @@ -216,6 +216,36 @@ else: self.rgb_widget.layout.addWidget(self.rgb_add_sliders, 2, 0) self.rgb_widget.layout.addWidget(self.rgb_mul_sliders, 2, 0) + #--------------------------------------------------------------- + # HSV sliders + #--------------------------------------------------------------- + + # radio buttons + self.hsv_add = QtGui.QRadioButton('Additive') + self.hsv_mul = QtGui.QRadioButton('Multiplicative') + self.hsv_mul.toggled.connect(self.hsv_radio_changed) + self.hsv_add.toggled.connect(self.hsv_radio_changed) + + # additive sliders + self.hsv_add_sliders = NSliderBlock(3, [(-360, 360, 0, 'H', 1), + (-100, 100, 0, 'S', .01), + (-100, 100, 0, 'V', .01)], + self.hsv_add_changed) + + # multiplicative sliders + self.hsv_mul_sliders = NSliderBlock(3, [(0, 1000, 500, 'H', .002), + (0, 1000, 500, 'S', .002), + (0, 1000, 500, 'V', .002)], + self.hsv_mul_changed) + + # layout + self.hsv_widget = QWidget() + self.hsv_widget.layout = QtGui.QGridLayout(self.hsv_widget) + self.hsv_widget.layout.addWidget(self.hsv_add, 0, 0) + self.hsv_widget.layout.addWidget(self.hsv_mul, 1, 0) + self.hsv_widget.layout.addWidget(self.hsv_add_sliders, 2, 0) + self.hsv_widget.layout.addWidget(self.hsv_mul_sliders, 2, 0) + #--------------------------------------------------------------- # Brightness sliders #--------------------------------------------------------------- @@ -244,6 +274,7 @@ else: self.layout = QtGui.QGridLayout(self) self.layout.addWidget(self.combo_box, 0, 0) self.layout.addWidget(self.rgb_widget, 1, 0) + self.layout.addWidget(self.hsv_widget, 1, 0) self.layout.addWidget(self.bright_widget, 1, 0) self.layout.addWidget(self.commit_button, 2, 0) self.layout.addWidget(self.revert_button, 3, 0) @@ -256,6 +287,7 @@ else: self.hide_sliders() self.rgb_widget.show() self.rgb_add.setChecked(True) + self.hsv_add.setChecked(True) def rgb_add_changed(self, name, val): if not self.rgb_add.isChecked(): @@ -283,6 +315,24 @@ else: return self.update() + def hsv_add_changed(self, name, val): + if not self.hsv_add.isChecked(): + return + h = self.hsv_add_sliders.sliders['H'].conv_val() + s = self.hsv_add_sliders.sliders['S'].conv_val() + v = self.hsv_add_sliders.sliders['V'].conv_val() + self.mixer.hsv_add(h, s, v) + self.update() + + def hsv_mul_changed(self, name, val): + if not self.hsv_mul.isChecked(): + return + h = self.hsv_mul_sliders.sliders['H'].conv_val() + s = self.hsv_mul_sliders.sliders['S'].conv_val() + v = self.hsv_mul_sliders.sliders['V'].conv_val() + self.mixer.hsv_multiply(h, s, v) + self.update() + def bright_changed(self, name, val): # doesnt matter which slider changed we need both # values @@ -295,6 +345,7 @@ else: def reset_sliders(self): self.rgb_add_sliders.set_sliders({'R': 0, 'G': 0, 'B': 0}) self.rgb_mul_sliders.set_sliders({'R': 500, 'G': 500, 'B': 500}) + self.hsv_add_sliders.set_sliders({'H': 0, 'S': 0, 'V': 0}) self.bright_sliders.set_sliders({'+': 0, 'x': 500}) def combo_box_changed(self, index): @@ -307,6 +358,7 @@ else: def hide_sliders(self): self.rgb_widget.hide() + self.hsv_widget.hide() self.bright_widget.hide() def rgb_radio_changed(self): @@ -323,12 +375,27 @@ else: self.mixer.set_to_stateimg() self.update() + def hsv_radio_changed(self): + if self.hsv_add.isChecked(): + self.hsv_add_sliders.show() + self.hsv_mul_sliders.hide() + elif self.hsv_mul.isChecked(): + self.hsv_mul_sliders.show() + self.hsv_add_sliders.hide() + else: + pass + + self.reset_sliders() + self.mixer.set_to_stateimg() + self.update() + def show_rgb(self): self.hide_sliders() self.rgb_widget.show() def show_hsv(self): self.hide_sliders() + self.hsv_widget.show() def show_bright(self): self.hide_sliders() diff --git a/scikits/image/io/_plugins/util.py b/scikits/image/io/_plugins/util.py index 8f32f4e5..2bc21819 100644 --- a/scikits/image/io/_plugins/util.py +++ b/scikits/image/io/_plugins/util.py @@ -244,4 +244,53 @@ class ColorMixer(object): _colormixer.multiply(self.img, self.stateimg, channel, ammount) def brightness(self, offset, factor): + '''Adjust the brightness off an image with an offset and factor. + + Parameters + ---------- + offset : integer + The ammount to add to each channel. + factor : float + The factor to multiply each channel by. + + result = clip((pixel + offset)*factor) + + ''' _colormixer.brightness(self.img, self.stateimg, offset, factor) + + def hsv_add(self, h_amt, s_amt, v_amt): + '''Adjust the H, S, V channels of an image by a constant ammount. + This is similar to the add() mixer function, but operates over the + entire image at once. Thus all three additive values, H, S, V, must + be supplied simultaneously. + + Parameters + ---------- + h_amt : float + The ammount to add to the hue (-360..360) + s_amt : float + The ammount to add to the saturation (-1..1) + v_amt : float + The ammount to add to the value (-1..1) + + ''' + _colormixer.hsv_add(self.img, self.stateimg, h_amt, s_amt, v_amt) + + def hsv_multiply(self, h_amt, s_amt, v_amt): + '''Adjust the H, S, V channels of an image by a constant ammount. + This is similar to the add() mixer function, but operates over the + entire image at once. Thus all three additive values, H, S, V, must + be supplied simultaneously. + + Parameters + ---------- + h_amt : float + The ammount to multiply to the hue (0..1) + s_amt : float + The ammount to multiply to the saturation (0..1) + v_amt : float + The ammount to multiply to the value (0..1) + + ''' + _colormixer.hsv_multiply(self.img, self.stateimg, h_amt, s_amt, v_amt) +