def test_that_pixelate_returns_pixelated_photo(self): pixelated_wolf = self.creator.pixelate(nr_pixels_in_x=50, nr_pixels_in_y=35) output_file = Path.to_testphoto('wolf_pixelated_50_35.bmp') if self.RESET_EXPECTED_OUTPUT: pixelated_wolf.save(output_file) # Expected image is a bitmap, to prevent jpeg artefacts in comparison expected_pixelated_wolf = Photo.open(output_file) self.assertEqual(expected_pixelated_wolf, pixelated_wolf)
def test_that_resize_images_resizes_all_images(self): self.setup_photo_dir_structure() collector = PhotoCollector(self.dirname) collector._split_by_size_ratio() collector._select_images(desired_size_ratio=100) # Crop the first image, such that the original is not square anymore mosaic_dir = os.path.join(self.photos_dir, 'mosaic') first_cat = os.path.join(mosaic_dir, '00000001.jpg') img = Photo.open(first_cat) img = img.crop((0, 0, 400, 200)) img.save(first_cat) collector._resize_images(desired_size_ratio=100, desired_width=100) for file in os.scandir(mosaic_dir): img = Photo.open(file.path) desired_size = (100, 100) self.assertTupleEqual(desired_size, img.size)
def photos(self) -> Dict[str, Photo]: if not self._photos: self._photos = { filename: Photo.open(os.path.join(self.src_dir, filename)) for filename in sorted(os.listdir(self.src_dir)) if self._is_image(filename) } if not self._photos: msg = f'No photos found in directory {self.src_dir}' raise FileNotFoundError(msg) return self._photos
def test_that_photo_pixelate_returns_photo_pixelated_photo(self): random.seed(1) pixelated_wolf = self.creator.photo_pixelate(src_dir=os.path.join( Path.testdata, 'cats'), nr_pixels_in_x=30, nr_pixels_in_y=30) output_file = Path.to_testphoto('wolf_photo_pixelated_30_30.bmp') if self.RESET_EXPECTED_OUTPUT: pixelated_wolf.save(output_file) # Expected image is a bitmap, to prevent jpeg artefacts in comparison expected_pixelated_wolf = Photo.open(output_file) self.assertEqual(expected_pixelated_wolf, pixelated_wolf)
def __init__(self, filepath: str, max_output_size: int = DEFAULT_MAX_OUTPUT_SIZE, cheat_parameter: int = DEFAULT_CHEAT_PARAMETER): """ :param filepath: Path to the file with the photo to recreate :param max_output_size: Maximum width or height of the output image :param cheat_parameter: Value between 0 (no cheat) and 255 (full cheat) to additionally color the photos in the original pixel's color """ assert 0 <= cheat_parameter <= 255 self.original_photo = Photo.open(filepath) self.original_size = self.original_photo.size self.pixels = list(self.original_photo.getdata()) self.max_output_size = max_output_size self.cheat_parameter = cheat_parameter self.output_size = self._determine_output_size()
def _split_by_size_ratio(self) -> int: """ Split the flat directory structure of images in `photos/<dirname>` into a directory per size ratio Size ratio is defined as width/height, multiplied by 100 and rounded to an integer In other words: 100 means 1:1, 160 means 8:5, etc. As a side effect, we write a file called `size_ratio.json` with per size ratio the amount of images with that size ratio. This can be used to manually inspect the various size ratios incase you receive surprising results. :return: The most common size ratio """ # Exhaust the iterator in order to not iterate over directories created in the for-loop files = list(os.scandir(self.photos_dir)) size_ratio_counter = defaultdict(int) for file in files: img = Photo.open(file.path) w, h = img.size resolution = int(round(w / h * 100)) dst_dirname = str(resolution) dst_dir = os.path.join(self.photos_dir, dst_dirname) if not os.path.isdir(dst_dir): os.mkdir(dst_dir) dst_full_path = os.path.join(dst_dir, file.name) size_ratio_counter[resolution] += 1 shutil.move(file.path, dst_full_path) # Sort by number of occurrences size_ratio_counter = Counter(size_ratio_counter).most_common() most_common = size_ratio_counter[0][0] size_ratio_counter = dict(size_ratio_counter) resolution_file = os.path.join(self.photos_dir, 'size_ratio.json') with open(resolution_file, 'w') as f: f.write(json.dumps(size_ratio_counter, indent=4)) return most_common
def _resize_images(self, desired_size_ratio: int, desired_width: int) -> None: """ Resize all images from photos/<dirname>/mosaic to the desired width :param desired_size_ratio: The desired size ratio for the final images (as defined in split_by_size_ratio) :param desired_width: The width of the final images to use in the MosaicCreator Rationale why we do not have desired_height as input: it needs to be calculated in a rather tedious way, and we don't want the client to take care of that. The desired_ """ # TODO: I have the feeling that this should not be done here. desired_height = int(round(desired_width / desired_size_ratio * 100)) desired_size: Size = (desired_width, desired_height) mosaic_dir = os.path.join(self.photos_dir, 'mosaic') files = list(os.scandir(mosaic_dir)) for file in files: img = Photo.open(file.path) img = img.resize(desired_size) img.save(file.path)
def test_that_real_photo_returns_correct_color(self): photo = Photo.open(Path.to_testphoto('wolf_low_res')) expected_avg_color = (127, 111, 102) self.assertTupleEqual(expected_avg_color, photo.avg_color)