その漫画自炊オタクはImageJマクロに恋をする

プログラミングを用いた、自炊漫画の画像処理

【改良版】コントラスト調整やリサイズ処理などなどを、一気にやってしまうためのMacro

  

f:id:yu3xx:20210522051919j:plain

 

以前紹介したお役立ち多目的マクロに、「コントラスト自動調整マクロoptimizeMinAndMax」をくっつけました。

 

 

何かと出番の多い汎用型マクロの最新版です!

 

//Almighty_Processing.txt
//ContrastAdjust (As necessary, for 8bit, FullColor)
//Optimize minAndMax (As necessary)
//SetResolution (As necessary, for any Type)
//NombreCut type1 (As necessary, UseOneROI)
//NombreCut type2 (As necessary, CaseDivided by ODD/EVEN, [number==0 -> error])
//Renumbering from1 (As necessary, Files must be in subDirectory)
//Renumbering (As necessary, Automater)
//MakeDirAuto (FileName : trueTitle_number.ext)


print("");
print("Almighty Processing");
print("makeDirAuto");
print("listFilesRecursively");


//Define variable
flagFullColor = 0;

//Select Processing
Dialog.create("Processing setting");
Dialog.addMessage("Select Processing");
Dialog.addMessage("");
Dialog.addMessage("- Main -");
Dialog.addCheckbox("Contrast Adjustment", false);
Dialog.addCheckbox("Change the Resolution",false);
Dialog.addCheckbox("Nombre Cut",false);
Dialog.addMessage("");
Dialog.addMessage("- Additional -");
Dialog.addMessage("");
Dialog.addCheckbox("Optimize minAndMax",false);
Dialog.addCheckbox("Renumbering [from 1]",false);
Dialog.addCheckbox("Renumbering [Automator]",false);
Dialog.show;
flagContr = Dialog.getCheckbox();
flagReso = Dialog.getCheckbox();
flagNombre = Dialog.getCheckbox();
flagOptMinMax = Dialog.getCheckbox();
flagRenumFrom1 = Dialog.getCheckbox();
flagRenumATMT = Dialog.getCheckbox();
//print
print("");
if(flagContr == 1) print("flagContra = ON");
if(flagReso == 1) print("flagReso = ON");
if(flagNombre == 1) print("flagNombre = ON");
if(flagOptMinMax == 1) print("flagOptMinMax = ON");
if(flagRenumFrom1 == 1) print("flagRenumFron1 = ON");
if(flagRenumATMT == 1) print("flagRenumATMT = ON");
print("");

if(flagRenumFrom1 == 1 && flagRenumATMT == 1) exit("Two \"Renumbering Processing\" were selected.");
if (flagContr == 0 && flagReso == 0 && flagNombre == 0) exit("You must select at least one processing!!");


//Do something for selected folder
showMessage("Select Processing Folder");
openDir = getDirectory("Choose a Directory");
print("Processing :",openDir);
selectWindow("Log");
showMessage("Select Save Folder");
saveDir = getDirectory("Choose a Directory");
print("Save to :",saveDir);
selectWindow("Log");
list = getFileList(openDir);
wait(1000);


//optimizeMinAndMax
var optimizedMin = 0;
var optimizedMax = 255;
if(flagOptMinMax == 1) optMinMax();


//Select Nombre Type
if(flagNombre == 1){
	Dialog.create("Nombre Cut setting");
	items = newArray("Use One ROI", "Case Divided by ODD/EVEN");
	Dialog.addRadioButtonGroup("Select NombreCut Type", items, 2, 1, "Use One ROI");
	Dialog.show;
	nombreRadio = Dialog.getRadioButton();
	if(nombreRadio == "Use One ROI") NombreType = 1;
	if(nombreRadio== "Case Divided by ODD/EVEN") NombreType = 2;
}

//Clear ROI Manager and set ROI
if(flagNombre == 1){
	roiCount = roiManager("count");
	if(roiCount > 0){
		roiManager("Deselect");
		roiManager("Delete");
	}
	if(NombreType == 1){
		//ROI setting
		waitForUser("ROI setting","Open an image and set [ROI] using rectangle tool, then click [OK].");
		roiManager("Add");
		close();
	}
	if(NombreType == 2){
		//ROI setting ODD(1,3,5,7,...)
		waitForUser("ODD(1,3,5,7,...)","Open an [ODD number] file and set [ROI] using rectangle tool, then click [OK].");
		roiManager("Add");
		close();

		//ROI setting EVEN(2,4,6,8,...)
		waitForUser("EVEN(2,4,6,8,...)","Open an [EVEN number] file and set [ROI] using rectangle tool, then click [OK].");
		roiManager("Add");
		close();
	}
}

//Enter parameter
Dialog.create("Processing Setting");
if(flagContr == 1) Dialog.addNumber("min (0-255)",optimizedMin);
if(flagContr == 1) Dialog.addNumber("Max (0-255)",optimizedMax);
if(flagContr == 1) Dialog.addCheckbox("Full Color", false);
if(flagContr == 1) Dialog.addMessage("");
if(flagReso == 1) Dialog.addNumber("pre dpi",600);
if(flagReso == 1) Dialog.addNumber("post dpi(Target)",400);
if(flagRenumATMT == 1 || flagRenumFrom1 == 1) Dialog.addNumber("Digit of Page Number",4);
items = newArray("JPEG", "PNG");
Dialog.addRadioButtonGroup("Output format", items, 1, 2, "JPEG");
Dialog.show;
if(flagContr == 1) min = Dialog.getNumber();
if(flagContr == 1) max = Dialog.getNumber();
if(flagContr == 1) flagFullColor = Dialog.getCheckbox();
if(flagReso == 1) preDpi = Dialog.getNumber();
if(flagReso == 1) postDpi = Dialog.getNumber();
if(flagRenumATMT == 1 || flagRenumFrom1 == 1) digit = Dialog.getNumber();
output = Dialog.getRadioButton();
//print
print("");
if(flagFullColor == 1) print("flagFullColor = ON");
if (flagContr == 1) print("min =",min,", Max =",max);
if (flagReso == 1) print("pre dpi =",preDpi,", Target dpi =",postDpi);

//Extension
if(output == "JPEG") ext = ".jpg";
if(output == "PNG") ext = ".png";

//JPEG,PNG quality setting(jpeg=90,gif=-1)
quality = 90;
run("Input/Output...", "jpeg=quality gif=-1 file=.csv save_column save_row");
if (output == "JPEG") print("JPEG quality =",quality);
print("");
wait(1000);

//SetResolution Setting
if(flagReso == 1) compressionRate = postDpi/preDpi;

//reference setting
var referLength;
if(flagRenumATMT == 1){
	referenceCheck(openDir);
}

//make parentDirectory
parentDir = saveDir+"postProc_"+getTimeStamp()+"/";
File.makeDirectory(parentDir);


//operation
startTime = whatTimeNow();
setBatchMode(true);
var totalFiles = 0;
var procCount = 0;

if (flagRenumFrom1 == 1){
	Table.create("Correspondence Table");
}
countFiles(openDir);
listFiles(openDir);

//clear ROI Manager
if(flagNombre == 1){
	roiManager("Deselect");
	roiManager("Delete");
}

//fin
if (flagRenumFrom1 == 1){
	Table.update;
}
finishTime = whatTimeNow();
print("");
if (flagContr == 1) print("min =",min,", Max =",max);
if (flagReso == 1) print("Compression Rate =",compressionRate);
if (output == "JPEG") print("JPEG quality =",quality);
print("Start Time .... ",startTime);
print("FinishTime ... ",finishTime);
print("oshimai");
beep();


//-----------------------------------------------------------------------------
//Define referenceCheck

function referenceCheck(dir){
	list = getFileList(dir);
	if(endsWith(list[0],"/")){
		referenceCheck(dir+list[0]);
	}else{
		name = list[0];
		dotIndex = lastIndexOf(name,".");
		title = substring(name,0,dotIndex);
		space = lastIndexOf(title," ");
		trueTitle = substring(title,0,space);
		number = substring(title,space+1);

		reference = trueTitle;
		referLength = lengthOf(reference);
		print("Check trueTitle. (e.g. NARUTO_60)");
		print("trueTitle=[",trueTitle,"]");
		showMessageWithCancel("Check trueTitle (except : number.ext), and click [OK].");
	}
}
//-----------------------------------------------------------------------------


//-----------------------------------------------------------------------------
//Define countFiles

function countFiles(dir){
	list = getFileList(dir);
	for(i=0; i<list.length; i++){
		if(endsWith(list[i],"/")){
			countFiles(dir+list[i]);
		}else{
			totalFiles++;
		}
	}
	
}
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
//Define listFiles

function listFiles(dir){
	list = getFileList(dir);
	for(i=0; i<list.length; i++){
		if(endsWith(list[i],"/")){
			listFiles(dir+list[i]);
		}else{
			open(dir+list[i]);
			operation();
		}
	}
	
}
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
//Define operation

function operation(){
	
	procCount++;
	
	//Rename Section
	if(flagRenumATMT == 1){
		name = getTitle();
		dotIndex = lastIndexOf(name,".");
		title = substring(name,0,dotIndex);

		//ReNumbering
		titleLength = lengthOf(title);

		if(referLength < titleLength){
			space = lastIndexOf(title," ");
			trueTitle = substring(title,0,space);
			number = substring(title,space+1);

			newname = trueTitle+"_"+zeroPad(number,digit)+ext;
		
		}else if(referLength == titleLength){

			newname = title+"_"+zeroPad(1,digit)+ext;

			dotIndex = lastIndexOf(newname,".");
			title = substring(newname,0,dotIndex);
			underBar = lastIndexOf(title,"_");
			trueTitle = substring(title,0,underBar);
			number = substring(title,underBar+1);
		}	
	}else if(flagRenumATMT == 0){
		name = getTitle();
		dotIndex = indexOf(name, ".");
		title = substring(name,0,dotIndex);
		underBar = lastIndexOf(title,"_");
		trueTitle = substring(title,0,underBar);
		number = substring(title,underBar+1);
		newname = title+ext;	
		if (flagRenumFrom1 == 1) newname = trueTitle+"_"+zeroPad(i+1,digit)+ext;
	}
	rename(newname);

	//Output Correspondence table
	if (flagRenumFrom1 == 1){
		Table.set("index",procCount-1,procCount);
		Table.set("Original",procCount-1,name);
		Table.set("Newname",procCount-1,newname);
	}
	//Nombre Cut
	if(flagNombre == 1){
		if(NombreType == 1){
			roiManager("Select", 0);
			run("Crop");
		}
		if(NombreType == 2){
			number = parseInt(number);
			if(number == 0) exit("Error!! Number must not be null.");
			//ODD(1,3,5,7,...)
			if(number%2 == 1){
				roiManager("Select", 0);
				run("Crop");
			}
			//EVEN(2,4,6,8,...)
			if(number%2 == 0){
				roiManager("Select", 1);
				run("Crop");
			}
		}
	}

	//ContrastAdjust 8 bit
	depth = bitDepth();
	if(flagFullColor == 0) {
		if (depth == 8) {
			//ContrastAdjust (As necessary)
			if (flagContr == 1) setMinAndMax(min, max);
			if (flagContr == 1) run("Apply LUT");
		}
	}
	if(flagFullColor == 1) {
		//ContrastAdjust (As necessary)
		if (flagContr == 1) setMinAndMax(min, max);
		if (flagContr == 1) run("Apply LUT");
	}

	//SetResolution
	if (flagReso == 1){
		width = getWidth;
		height = getHeight;
		targetW = floor(width*compressionRate);
		run("Size...", "width=targetW constrain average interpolation=Bicubic");
	}

	//Progress.the second decimal place by 10000/100
	print(procCount,"/",totalFiles,"...Progress=",floor(procCount/totalFiles*10000)/100,"%");
	
	//Save to SubDirectory
	subDir = parentDir+trueTitle+"/";
	if(!File.exists(subDir)){
		File.makeDirectory(subDir);
	}
	
	saveAs(output, subDir+newname);
	print("Save to...",subDir+newname);

	close(newname);
}
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
//Define optMinMax

function optMinMax(){

print("");
print("OptimizeMinAndMax");

if(nImages == 0) waitForUser("OptMinAndMax.  Open an image, then click [OK]"); 

//Enter parameter
Dialog.create("OptMinAndMax Setting");
Dialog.addNumber("targetMeanWhite",254.97);
Dialog.addNumber("targetMeanBlack",27); //default->18
Dialog.show;
targetMeanWhite = Dialog.getNumber();
targetMeanBlack = Dialog.getNumber();


name = getTitle();
showMessage("Select Desktop  (OptimizeMinAndMax) ");
saveDir = getDirectory("OptimizeMinAndMax. Choose Desktop");
print("");
print("Your Desktop is...");
print(saveDir);
print("");
saveAs("Jpeg", saveDir+"CheckMaxAndMin.jpg");

//optimize Max

setTool("rectangle");
run("Specify...", "width=100 height=100 x=10 y=10");
waitForUser("ROI selection","Set ROI on the \"WHITE BACK GROUND\", then click [OK].");
getStatistics(area,mean);

setBatchMode(true);

if(mean < 150){
	exit("Error!! This area is not WHITE BACK GROUND.");
}

run("Crop");


saveAs("Jpeg", saveDir+"CheckMax.jpg");
close();

Max = 254;
min = 0;

print("optimized Max Macro, TargetMean =",targetMeanWhite);


for (i=0; i<254; i++){
	open(saveDir+"CheckMax.jpg");
	setMinAndMax(min, Max);
	run("Apply LUT");
	getStatistics(area,mean);
	print("Max=",Max," mean=",mean);
	if(mean >= targetMeanWhite){
		optimizedMax = Max;
		i = 255; //break
	}
	Max = Max-1;
	close();
}

print("OptimizedMax =",optimizedMax);

setBatchMode(false);

//optimize min

open(saveDir+"CheckMaxAndMin.jpg");

setTool("rectangle");
run("Specify...", "width=100 height=100 x=10 y=10");
waitForUser("ROI selection","Set ROI on the \"BLACK ZONE\", then click [OK].");
getStatistics(area,mean);

setBatchMode(true);

if(mean > 100){
	exit("Error!! This area is not BLACK ZONE.");
}

run("Crop");


saveAs("Jpeg", saveDir+"CheckMin.jpg");
close();

max = optimizedMax;
min = 1;

print("");
print("OptimizedMin Macro, TargetMean=",targetMeanBlack);


for (i=0; i<254; i++){
	open(saveDir+"CheckMin.jpg");
	setMinAndMax(min, max);
	run("Apply LUT");
	getStatistics(area,mean);
	print("min=",min," mean=",mean);
	if(mean <= targetMeanBlack){
		optimizedMin = min;
		i=255; //break
	}
	min = min+1;
	close();
}

print("");
print("Title =",name);
print("OptimizedMin =",optimizedMin);
print("OptimizedMax =",optimizedMax);
print("oshimai");
setBatchMode(false);

} //function end

//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
//Define function getTimeStamp

function getTimeStamp(){
	getDateAndTime(year,month,dayOfWeek,dayOfMonth,hour,minute,second,msec);
	timeStamp = "string";
	strYear = ""+year;
	month = month+1;
	if(month < 10){
		strMonth = "0"+month;
	}else{
		strMonth = ""+month;
	}
	if(dayOfMonth < 10){
		strDayOfMonth = "0"+dayOfMonth;
	}else{
		strDayOfMonth = ""+dayOfMonth;
	}
	if(hour < 10){
		strHour = "0"+hour;
	}else{
		strHour = ""+hour;
	}
	if(minute < 10){
		strMinute = "0"+minute;
	}else{
		strMinute = ""+minute;
	}
	if(second < 10){
		strSecond = "0"+second;
	}else{
		strSecond = ""+second;
	}
	timeStamp = strYear+strMonth+strDayOfMonth+"_"+strHour+strMinute+strSecond;
	return timeStamp;
}
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
//Define function whatTimeNow

function whatTimeNow(){
	getDateAndTime(year,month,dayOfWeek,dayOfMonth,hour,minute,second,msec);
	stringTime = "string";
	strYear = ""+year;
	month = month+1;
	if(month < 10){
		strMonth = "0"+month;
	}else{
		strMonth = ""+month;
	}
	if(dayOfMonth < 10){
		strDayOfMonth = "0"+dayOfMonth;
	}else{
		strDayOfMonth = ""+dayOfMonth;
	}
	if(hour < 10){
		strHour = "0"+hour;
	}else{
		strHour = ""+hour;
	}
	if(minute < 10){
		strMinute = "0"+minute;
	}else{
		strMinute = ""+minute;
	}
	if(second < 10){
		strSecond = "0"+second;
	}else{
		strSecond = ""+second;
	}
	stringTime = strYear+"/"+strMonth+"/"+strDayOfMonth+"_"+strHour+":"+strMinute+":"+strSecond;
	return stringTime;
}

//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
//Define function zeroPadding

function zeroPad(int,digitZeroPad){
	if(int < 0){
		exit("ZeroPadding Error!!  int<0");
	}
	stringInt = ""+int;
	digitStringInt = lengthOf(stringInt);
	digitSubtra = digitZeroPad-digitStringInt;
	if(digitSubtra < 0){
		exit("ZeroPadding Error!!  digitSubtra<0");
	}
	if(digitZeroPad > 0){
		for(i=0; i<digitSubtra; i++){
			stringInt = "0"+stringInt;
		}
	}
	return stringInt;
}	

//-----------------------------------------------------------------------------

 

新機能

 

① 適切なコントラストと濃度を自動で調べてくれるマクロ、「Optimize Min And Max」を搭載

 

「Optimize Min And Max」で調べた値をそのまま「Contrast Adjustment」に引き継いでくれるので、少し作業がラクになります。min値Max値入力ダイアログの初期値として引き継ぐので、微調整も可能です。

 

そのままコードコピペだけで使えるように、「function optMinMax」内のsaveDirのデスクトップ指定を「ディレクトリをそのつど選択」の形式にしています。いちいち面倒だと思うので、コードをいじれる方は「saveDir = getDirectoryなにがし」のところを、「saveDir = /Users/yu3xx/Desktop/」とかに書き換えると幸せになれると思います。

 

ちなみにここで保存するcheckMaxなどの3つのファイルは、解析に使う目的で一時的に保存しているだけなので、あとで捨ててOKなファイルです。

 

② フルカラー画像に対するコントラスト・濃度調整を実装

 

地味に新機能です。いままでの「Contrast Adjustment」は白黒画像に対してでしたが、フルカラー画像に対しても、min値Max値の調整で簡易的にコントラスト・濃度を調整できるようにしました。使いたい場合はパラメータ設定画面で[Full Color]にチェック。

 

※ 残念なことに、Optimize Min And Maxはフルカラーには対応していないので、あらかじめ適切なmin値Max値を調べておいてください。手作業です。

f:id:yu3xx:20210522043539p:plain

使い方のポイント

 

① 起動すると処理選択ウインドウが表示されますので、必要なものだけチェックしてください。

f:id:yu3xx:20210522043533p:plain

② 選択したProcessing Folderの中に入っているサブフォルダ内のデータまで全て処理します(ListFiles)。 

 

フォルダ自動生成を命令しているので、各ファイルの名前は「trueTitle_number.拡張子」じゃないと動きません。(numberの前のアンダーバーが大事!)

例)僕のヒーローアカデミア_07_0149.png

 trueTitle = 僕のヒーローアカデミア_07

 number = 0149

 ※Renumbering [Automator] は例外です。numberの前にspaceが来るはずです。

 

④ 各処理の細かな解説は、以下のページを参考にしてみてください。

 

 ・Contrast Adjustment

コントラスト・濃度を調整するMacro - その漫画自炊オタクはImageJマクロに恋をする

 

 ・Optimize Min And Max 

コントラスト調整に必要なMax値とMin値を自動で求めるためのMacro - その漫画自炊オタクはImageJマクロに恋をする

 

 ・Change the Resolution

解像度を変更するMacro - その漫画自炊オタクはImageJマクロに恋をする

  

 ・Nombre Cut

実用版!ノンブル領域を削除し, コントラスト調整, 解像度変更も一括で行えるMacro - その漫画自炊オタクはImageJマクロに恋をする

 

・Renumbering [from 1] 

【多目的】コントラスト調整やリサイズ処理などなどを、一気にやってしまうためのMacro - その漫画自炊オタクはImageJマクロに恋をする

 

 ・Renumbering [Automator]

【Mac】実用版!PDFからJPEGに変換した画像のコントラスト調整+フォルダ収納のためのMacro - その漫画自炊オタクはImageJマクロに恋をする

 

  

マクロの起動方法

 

①ImageJ上部タブの[Plugins]→[New]→[Macro]で起動したエディタに、記事の一番上のコードをコピペしてtxtファイル(Almighty_Processing.txt)を作成・保存する。 

 

②保存したファイルをImageJフォルダ内の[plugins]フォルダにしまう。

このとき、[plugins]フォルダの中に新たに適当な名前のフォルダを作って、その中にしまってもOKです。ここでは仮に「自炊」というフォルダにtxtファイルを突っ込んだとします。

 

③一度ImageJを再起動すると、マクロがインストールされ、起動準備OK。

 

④上部タブ[Plugins]→[自炊]→[Almighty Processing]でマクロが実行されます。

 

 

imagej-jisui.hatenablog.com

 

 

 

 

ライセンスなんかは一切無いので、ぜひぜひ自由に使ってみてください!

  

imagej-jisui.hatenablog.com