Spaces:
Sleeping
Sleeping
| import cv2 | |
| import numpy as np | |
| class StudioService: | |
| """ | |
| Handles standard image editing operations. | |
| CRITICAL: Always expects the ORIGINAL image as input to prevent double-processing. | |
| """ | |
| def process_request(self, image_path, output_path, options): | |
| try: | |
| img = cv2.imread(image_path) | |
| if img is None: | |
| raise ValueError("Could not load image.") | |
| # 1. Geometry (Rotate/Flip) | |
| # Note: We apply this first so filters apply to the correct orientation | |
| img = self._apply_geometry(img, options) | |
| # 2. Adjustments (Brightness/Contrast/Sharpness) | |
| img = self._apply_adjustments(img, options) | |
| # 3. Filters | |
| filter_type = options.get('filter', 'none') | |
| if filter_type != 'none': | |
| img = self._apply_filter(img, filter_type) | |
| cv2.imwrite(output_path, img) | |
| return True, output_path | |
| except Exception as e: | |
| print(f"Studio Error: {e}") | |
| return False, str(e) | |
| def _apply_geometry(self, img, options): | |
| rotation = int(options.get('rotation', 0)) % 360 | |
| if rotation == 90: img = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE) | |
| elif rotation == 180: img = cv2.rotate(img, cv2.ROTATE_180) | |
| elif rotation == 270: img = cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE) | |
| if options.get('flip_h'): img = cv2.flip(img, 1) | |
| if options.get('flip_v'): img = cv2.flip(img, 0) | |
| return img | |
| def _apply_adjustments(self, img, options): | |
| # Brightness & Contrast | |
| bright = int(options.get('brightness', 0)) | |
| contrast = int(options.get('contrast', 0)) | |
| if bright != 0 or contrast != 0: | |
| if contrast == -100: alpha = 0.0 | |
| else: alpha = 131 * (contrast + 127) / (127 * (131 - contrast)) | |
| beta = bright | |
| img = cv2.convertScaleAbs(img, alpha=alpha, beta=beta) | |
| # Saturation | |
| sat = int(options.get('saturation', 0)) | |
| if sat != 0: | |
| # Convert to HSV, handling potential grayscale images | |
| if len(img.shape) == 2: img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) | |
| hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV).astype("float32") | |
| (h, s, v) = cv2.split(hsv) | |
| s = s * (1 + sat / 100.0) | |
| s = np.clip(s, 0, 255) | |
| hsv = cv2.merge([h, s, v]) | |
| img = cv2.cvtColor(hsv.astype("uint8"), cv2.COLOR_HSV2BGR) | |
| # Sharpness (Unsharp Masking) | |
| sharp = int(options.get('sharpness', 0)) | |
| if sharp != 0: | |
| # Sigma=3.0, Threshold=0 | |
| gaussian = cv2.GaussianBlur(img, (0, 0), 3.0) | |
| amount = sharp / 50.0 # Scale 0-100 to 0-2.0 strength | |
| img = cv2.addWeighted(img, 1 + amount, gaussian, -amount, 0) | |
| return img | |
| def _apply_filter(self, img, f_type): | |
| # Ensure image is 3-channel for filters | |
| if len(img.shape) == 2: img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) | |
| if f_type == 'grayscale': | |
| return cv2.cvtColor(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), cv2.COLOR_GRAY2BGR) | |
| elif f_type == 'sepia': | |
| kernel = np.array([[0.272, 0.534, 0.131], [0.349, 0.686, 0.168], [0.393, 0.769, 0.189]]) | |
| return cv2.transform(img, kernel) | |
| elif f_type == 'blur': | |
| return cv2.GaussianBlur(img, (15, 15), 0) | |
| elif f_type == 'negative': | |
| return cv2.bitwise_not(img) | |
| elif f_type == 'cyberpunk': | |
| img = img.astype(np.float32) | |
| b, g, r = cv2.split(img) | |
| b = np.clip(b * 1.2 + 10, 0, 255) | |
| r = np.clip(r * 1.1 + 10, 0, 255) | |
| return cv2.merge([b, g, r]).astype(np.uint8) | |
| elif f_type == 'sketch': | |
| gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) | |
| inv = cv2.bitwise_not(gray) | |
| blur = cv2.GaussianBlur(inv, (21, 21), 0) | |
| return cv2.cvtColor(cv2.divide(gray, 255 - blur, scale=256), cv2.COLOR_GRAY2BGR) | |
| elif f_type == 'cartoon': | |
| gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) | |
| gray = cv2.medianBlur(gray, 5) | |
| edges = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 9, 9) | |
| color = cv2.bilateralFilter(img, 9, 300, 300) | |
| return cv2.bitwise_and(color, cv2.cvtColor(edges, cv2.COLOR_GRAY2BGR)) | |
| # Fallback for others (summer, winter, etc) or return original | |
| return img | |