# -*- coding: utf-8 -*- # # Copyright (c) 2012-2014 Ciro Mattia Gonano # Copyright (c) 2013-2014 Pawel Jastrzebski # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . __version__ = '4.0.2' __license__ = 'GPL-3' __copyright__ = '2012-2014, Ciro Mattia Gonano , Pawel Jastrzebski ' __docformat__ = 'restructuredtext en' import os import sys from shutil import rmtree, copytree, move from optparse import OptionParser, OptionGroup from multiprocessing import Pool from PIL import Image, ImageStat from .shared import getImageFileName, walkLevel try: from PyQt5 import QtCore except ImportError: QtCore = None def mergeDirectory_tick(output): if output: mergeWorkerOutput.append(output) mergeWorkerPool.terminate() if GUI: GUI.progressBarTick.emit('tick') if not GUI.conversionAlive: mergeWorkerPool.terminate() def mergeDirectory(work): try: directory = work[0] images = [] imagesClear = [] sizes = [] h = 0 for root, dirs, files in walkLevel(directory, 0): for name in files: if getImageFileName(name) is not None: i = Image.open(os.path.join(root, name)) images.append([os.path.join(root, name), i.size[0], i.size[1]]) sizes.append(i.size[0]) if len(images) > 0: mw = max(set(sizes), key=sizes.count) for i in images: if i[1] == mw: h += i[2] imagesClear.append(i[0]) # Silently drop directories that contain too many images if h > 262144: return None result = Image.new('RGB', (mw, h)) y = 0 for i in imagesClear: img = Image.open(i) img = img.convert('RGB') result.paste(img, (0, y)) y += img.size[1] os.remove(i) savePath = os.path.split(imagesClear[0]) result.save(os.path.join(savePath[0], os.path.splitext(savePath[1])[0] + '.png'), 'PNG') except Exception: return str(sys.exc_info()[1]) def sanitizePanelSize(panel, opt): newPanels = [] if panel[2] > 8 * opt.height: diff = int(panel[2] / 8) newPanels.append([panel[0], panel[1] - diff*7, diff]) newPanels.append([panel[1] - diff*7, panel[1] - diff*6, diff]) newPanels.append([panel[1] - diff*6, panel[1] - diff*5, diff]) newPanels.append([panel[1] - diff*5, panel[1] - diff*4, diff]) newPanels.append([panel[1] - diff*4, panel[1] - diff*3, diff]) newPanels.append([panel[1] - diff*3, panel[1] - diff*2, diff]) newPanels.append([panel[1] - diff*2, panel[1] - diff, diff]) newPanels.append([panel[1] - diff, panel[1], diff]) elif panel[2] > 4 * opt.height: diff = int(panel[2] / 4) newPanels.append([panel[0], panel[1] - diff*3, diff]) newPanels.append([panel[1] - diff*3, panel[1] - diff*2, diff]) newPanels.append([panel[1] - diff*2, panel[1] - diff, diff]) newPanels.append([panel[1] - diff, panel[1], diff]) elif panel[2] > 2 * opt.height: newPanels.append([panel[0], panel[1] - int(panel[2] / 2), int(panel[2] / 2)]) newPanels.append([panel[1] - int(panel[2] / 2), panel[1], int(panel[2] / 2)]) else: newPanels = [panel] return newPanels def splitImage_tick(output): if output: splitWorkerOutput.append(output) splitWorkerPool.terminate() if GUI: GUI.progressBarTick.emit('tick') if not GUI.conversionAlive: splitWorkerPool.terminate() #noinspection PyUnboundLocalVariable def splitImage(work): try: path = work[0] name = work[1] opt = work[2] # Hardcoded options threshold = 1.0 delta = 15 fileExpanded = os.path.splitext(name) filePath = os.path.join(path, name) image = Image.open(filePath) image = image.convert('RGB') widthImg, heightImg = image.size if heightImg > opt.height: if opt.debug: from PIL import ImageDraw debugImage = Image.open(filePath) draw = ImageDraw.Draw(debugImage) # Find panels y1 = 0 y2 = 15 panels = [] while y2 < heightImg: while ImageStat.Stat(image.crop([0, y1, widthImg, y2])).var[0] < threshold and y2 < heightImg: y2 += delta y2 -= delta y1Temp = y2 y1 = y2 + delta y2 = y1 + delta while ImageStat.Stat(image.crop([0, y1, widthImg, y2])).var[0] >= threshold and y2 < heightImg: y1 += delta y2 += delta if y1 + delta >= heightImg: y1 = heightImg - 1 y2Temp = y1 if opt.debug: draw.line([(0, y1Temp), (widthImg, y1Temp)], fill=(0, 255, 0)) draw.line([(0, y2Temp), (widthImg, y2Temp)], fill=(255, 0, 0)) panelHeight = y2Temp - y1Temp if panelHeight > delta: # Panels that can't be cut nicely will be forcefully splitted panelsCleaned = sanitizePanelSize([y1Temp, y2Temp, panelHeight], opt) for panel in panelsCleaned: panels.append(panel) if opt.debug: debugImage.save(os.path.join(path, fileExpanded[0] + '-debug.png'), 'PNG') # Create virtual pages pages = [] currentPage = [] pageLeft = opt.height panelNumber = 0 for panel in panels: if pageLeft - panel[2] > 0: pageLeft -= panel[2] currentPage.append(panelNumber) panelNumber += 1 else: if len(currentPage) > 0: pages.append(currentPage) pageLeft = opt.height - panel[2] currentPage = [panelNumber] panelNumber += 1 if len(currentPage) > 0: pages.append(currentPage) # Create pages pageNumber = 1 for page in pages: pageHeight = 0 targetHeight = 0 for panel in page: pageHeight += panels[panel][2] if pageHeight > delta: newPage = Image.new('RGB', (widthImg, pageHeight)) for panel in page: panelImg = image.crop([0, panels[panel][0], widthImg, panels[panel][1]]) newPage.paste(panelImg, (0, targetHeight)) targetHeight += panels[panel][2] newPage.save(os.path.join(path, fileExpanded[0] + '-' + str(pageNumber) + '.png'), 'PNG') pageNumber += 1 os.remove(filePath) except Exception: return str(sys.exc_info()[1]) def Copyright(): print(('comic2panel v%(__version__)s. Written by Ciro Mattia Gonano and Pawel Jastrzebski.' % globals())) def main(argv=None, qtGUI=None): global options, GUI, splitWorkerPool, splitWorkerOutput, mergeWorkerPool, mergeWorkerOutput parser = OptionParser(usage="Usage: kcc-c2p [options] comic_folder", add_help_option=False) mainOptions = OptionGroup(parser, "MANDATORY") otherOptions = OptionGroup(parser, "OTHER") mainOptions.add_option("-y", "--height", type="int", dest="height", default=0, help="Height of the target device screen") mainOptions.add_option("-i", "--in-place", action="store_true", dest="inPlace", default=False, help="Overwrite source directory") mainOptions.add_option("-m", "--merge", action="store_true", dest="merge", default=False, help="Combine every directory into a single image before splitting") otherOptions.add_option("-d", "--debug", action="store_true", dest="debug", default=False, help="Create debug file for every splitted image") otherOptions.add_option("-h", "--help", action="help", help="Show this help message and exit") parser.add_option_group(mainOptions) parser.add_option_group(otherOptions) options, args = parser.parse_args(argv) if qtGUI: GUI = qtGUI else: GUI = None if len(args) != 1: parser.print_help() return if options.height > 0: options.sourceDir = args[0] options.targetDir = args[0] + "-Splitted" if os.path.isdir(options.sourceDir): rmtree(options.targetDir, True) copytree(options.sourceDir, options.targetDir) work = [] pagenumber = 1 splitWorkerOutput = [] splitWorkerPool = Pool() if options.merge: print("\nMerging images...") directoryNumer = 1 mergeWork = [] mergeWorkerOutput = [] mergeWorkerPool = Pool() mergeWork.append([options.targetDir]) for root, dirs, files in os.walk(options.targetDir, False): for directory in dirs: directoryNumer += 1 mergeWork.append([os.path.join(root, directory)]) if GUI: GUI.progressBarTick.emit('Combining images') GUI.progressBarTick.emit(str(directoryNumer)) for i in mergeWork: mergeWorkerPool.apply_async(func=mergeDirectory, args=(i, ), callback=mergeDirectory_tick) mergeWorkerPool.close() mergeWorkerPool.join() if GUI and not GUI.conversionAlive: rmtree(options.targetDir, True) raise UserWarning("Conversion interrupted.") if len(mergeWorkerOutput) > 0: rmtree(options.targetDir, True) raise RuntimeError("One of workers crashed. Cause: " + mergeWorkerOutput[0]) print("\nSplitting images...") for root, dirs, files in os.walk(options.targetDir, False): for name in files: if getImageFileName(name) is not None: pagenumber += 1 work.append([root, name, options]) else: os.remove(os.path.join(root, name)) if GUI: GUI.progressBarTick.emit('Splitting images') GUI.progressBarTick.emit(str(pagenumber)) GUI.progressBarTick.emit('tick') if len(work) > 0: for i in work: splitWorkerPool.apply_async(func=splitImage, args=(i, ), callback=splitImage_tick) splitWorkerPool.close() splitWorkerPool.join() if GUI and not GUI.conversionAlive: rmtree(options.targetDir, True) raise UserWarning("Conversion interrupted.") if len(splitWorkerOutput) > 0: rmtree(options.targetDir, True) raise RuntimeError("One of workers crashed. Cause: " + splitWorkerOutput[0]) if options.inPlace: rmtree(options.sourceDir) move(options.targetDir, options.sourceDir) else: rmtree(options.targetDir, True) raise UserWarning("Source directory is empty.") else: raise UserWarning("Provided path is not a directory.") else: raise UserWarning("Target height is not set.")