diff --git a/cdisplayagain.py b/cdisplayagain.py index a755ea2..31c0bcd 100644 --- a/cdisplayagain.py +++ b/cdisplayagain.py @@ -1302,14 +1302,48 @@ def _page_counter_text(self) -> str | None: return f"{self._current_index + 1}/{total}" def _page_counter_color(self) -> str: - """Pick black or white text based on the mean luminance of the current page.""" + """Pick black or white text based on the luminance of the page area where the counter sits. + + The counter is drawn at the bottom-right of the canvas. Sample that region + (after accounting for image scaling/positioning) so the text contrasts with + whatever is actually behind it. + """ if self._current_pil is None: return "#ffffff" try: from PIL import ImageStat - thumb = self._current_pil.convert("L").resize((32, 32), Image.Resampling.BILINEAR) - mean = ImageStat.Stat(thumb).mean[0] + cw = max(1, self.canvas.winfo_width()) + ch = max(1, self.canvas.winfo_height()) + if self._scaled_size is None: + thumb = self._current_pil.convert("L").resize((32, 32), Image.Resampling.BILINEAR) + mean = ImageStat.Stat(thumb).mean[0] + return "#000000" if mean >= 128 else "#ffffff" + + iw, ih = self._scaled_size + margin = 12 + cx = cw - margin + cy = ch - margin + + image_left = (cw - iw) // 2 + image_top = (ch - ih) // 2 if ih <= ch else -self._scroll_offset + img_x = max(0, min(iw - 1, cx - image_left)) + img_y = max(0, min(ih - 1, cy - image_top)) + + sample_size = min(32, iw // 4, ih // 4) + if sample_size < 4: + sample_size = min(4, iw, ih) + left = max(0, img_x - sample_size) + top = max(0, img_y - sample_size) + right = min(iw, img_x + sample_size) + bottom = min(ih, img_y + sample_size) + + if left >= right or top >= bottom: + thumb = self._current_pil.convert("L").resize((32, 32), Image.Resampling.BILINEAR) + mean = ImageStat.Stat(thumb).mean[0] + else: + region = self._current_pil.crop((left, top, right, bottom)).convert("L") + mean = ImageStat.Stat(region).mean[0] except Exception: return "#ffffff" return "#000000" if mean >= 128 else "#ffffff" diff --git a/tests/test_page_counter.py b/tests/test_page_counter.py index 27fc8b9..e4a6136 100644 --- a/tests/test_page_counter.py +++ b/tests/test_page_counter.py @@ -316,3 +316,77 @@ def test_render_current_sync_no_source_clears_counter(tk_root, tmp_path): viewer._render_current_sync() assert viewer._page_counter_id is None + + +def test_counter_color_sampling_logic(): + """Test the coordinate sampling logic directly.""" + from PIL import Image, ImageStat + + def test_sampling(img, cw, ch, iw, ih, scroll_offset=0): + """Test sampling logic for given image and dimensions.""" + margin = 12 + cx = cw - margin + cy = ch - margin + + image_left = (cw - iw) // 2 + image_top = (ch - ih) // 2 if ih <= ch else -scroll_offset + img_x = max(0, min(iw - 1, cx - image_left)) + img_y = max(0, min(ih - 1, cy - image_top)) + + sample_size = min(32, iw // 4, ih // 4) + if sample_size < 4: + sample_size = min(4, iw, ih) + left = max(0, img_x - sample_size) + top = max(0, img_y - sample_size) + right = min(iw, img_x + sample_size) + bottom = min(ih, img_y + sample_size) + + region = img.crop((left, top, right, bottom)).convert("L") + return ImageStat.Stat(region).mean[0] + + # Test 1: Normal size image (fits in canvas) + # Create a dark page (100x100) with a white border in the bottom-right + img1 = Image.new("RGB", (100, 100), color=(0, 0, 0)) + # Add white border in bottom-right 30x30 area + for x in range(70, 100): + for y in range(70, 100): + img1.putpixel((x, y), (255, 255, 255)) + + # Test with realistic canvas size + cw, ch = 800, 600 + iw, ih = 100, 100 + mean1 = test_sampling(img1, cw, ch, iw, ih) + + # The counter area should be white, so text should be black + assert mean1 >= 128 # white area + + # Test 2: Light page with dark border + img2 = Image.new("RGB", (100, 100), color=(255, 255, 255)) + # Add dark border in bottom-right 30x30 area + for x in range(70, 100): + for y in range(70, 100): + img2.putpixel((x, y), (0, 0, 0)) + + mean2 = test_sampling(img2, cw, ch, iw, ih) + # The counter area should be dark, so text should be white + assert mean2 < 128 # dark area + + # Test 3: Tall image (doesn't fit in canvas) - regression test for issue #58 + # Create a tall dark page (100x1200) with white border at the bottom + img3 = Image.new("RGB", (100, 1200), color=(0, 0, 0)) + # Add white border in bottom 30 rows + for x in range(0, 100): + for y in range(1170, 1200): + img3.putpixel((x, y), (255, 255, 255)) + + # Test with scroll offset = 0 (showing top of image) + cw, ch = 800, 600 + iw, ih = 100, 1200 + mean3 = test_sampling(img3, cw, ch, iw, ih, scroll_offset=0) + # Counter is at bottom of canvas, but we're showing top of image, so it should be dark + assert mean3 < 128 # dark area + + # Test with scroll offset = 600 (showing bottom of image) + mean4 = test_sampling(img3, cw, ch, iw, ih, scroll_offset=600) + # Counter is at bottom of canvas, showing bottom of image, so it should be white + assert mean4 >= 128 # white area