東方の当たり判定を描画してみるよ!

完全にゲームハックなので多少はぐらかして書きます。そうするくらいなら公開するなという話ですが自己顕示欲を抑えきれなかった。

  • まずやること
    • 敵、自機の座標が格納されているメモリを探す
    • 当たり判定処理を読んでどんな風になっているのかを把握する

本当はAIを作りたかった。しかし飽きた。
AIのデバッグ用に当たり判定の描画を作ってた。

  • どう実現するか
    • DLLインジェクションを使う。パラサイトルーチン書くのがとても楽。

どういう風にDLLインジェクションするかだが、出来るだけ元のプロセスのメモリを弄りたくない。
そこで毎フレーム呼び出される関数を見つけて、そこをDLL内の関数で上書きすることにした。
こうするとAttach時に関数を書き換え、Detach時に戻すことで復帰も容易。

毎フレーム呼び出される関数だが、これはキー情報を取得する関数(以下getKeyState())を使った。

インジェクションするDLLのコード。

EXPORT void frame_wrapper(){
	char buf[256];
    //スタックの処理
	__asm{
		push DWORD PTR SS:[ebp+8];
		call getKeyState;
		add esp, 4;
		pushad;
		pushfd;
	}
    //ここにやりたい処理を書く。AIの関数とかが呼ばれてた。
    __asm{
		popfd;
		popad;
	}
}

void attach(){
    //このDLLからExportしている上書き用関数
	int p_frame_wrapper = (int)GetProcAddress(h_hook, "frame_wrapper");
	//getKeyStateの関数ポインタ
	p_getKeyState;
	getKeyState = *(int *)p_getKeyState;
	*(int *)(p_getKeyState) = p_frame_wrapper;
}

void detach(){
	*(int *)(p_getKeyState) = getKeyState;
}

BOOL WINAPI DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID lpReserved){
	switch (reasonForCall){
	case DLL_PROCESS_ATTACH:
		OutputDebugString("[*]Dll Attach");
		attach();
		break;
	case DLL_PROCESS_DETACH:
		OutputDebugString("[*]Dll Detach");
		detach();
		break;
		
	}
	return TRUE;
}

インジェクションするためのexeのコード

#include <Windows.h>
#include <iostream>
#include <string>
#include <tlhelp32.h>

using namespace std;

int main(){
	char buf[256];
	DWORD pid = 0;
	char libpath[] = "hook.dll";
	
	PROCESSENTRY32 pe;
	HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	Process32First(hSnap, &pe);
	do{
		if(!strcmp("東方紅魔郷.exe", pe.szExeFile)){
			pid = pe.th32ProcessID;
			break;
		}
	}while(Process32Next(hSnap, &pe));
	
	if(!pid){
		cout << "紅魔郷を起動してください" << endl;
		return 0;
	}
	
	sprintf(buf, "[*]pid: %d", pid);
	OutputDebugString(buf);

	HANDLE proc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
	
	HMODULE hm = GetModuleHandle("kernel32");
	LPTHREAD_START_ROUTINE p_LoadLibrary = (LPTHREAD_START_ROUTINE)GetProcAddress(hm, "LoadLibraryA");
	printf("p_LoadLibrary:%X\n", p_LoadLibrary);

	LPTHREAD_START_ROUTINE p_FreeLibrary = (LPTHREAD_START_ROUTINE)GetProcAddress(hm, "FreeLibrary");
	printf("p_FreeLibrary:%X\n", p_FreeLibrary);

	int pathSize = strlen(libpath) + 1;
	LPVOID remoteLibPath = VirtualAllocEx(proc, NULL, pathSize, MEM_COMMIT, PAGE_READWRITE);
	WriteProcessMemory(proc, remoteLibPath, libpath, pathSize, NULL);

	char option[256];
	HANDLE hThread;
	while(1){
		scanf("%s", option);
		if(!strcmp(option, "load")){
			cout << "[*] Loading DLL" << endl;
			hThread = CreateRemoteThread(proc, NULL, 0, p_LoadLibrary, remoteLibPath, 0, NULL);

		}else if(!strcmp(option, "unload")){
			cout << "[*] Unloading DLL" << endl;
			DWORD exCode;
			GetExitCodeThread(hThread, &exCode);
			CreateRemoteThread(proc, NULL, 0, p_FreeLibrary, (LPDWORD)exCode, 0, NULL);
		}
	}
	return 0;
}

exeの方のコードは普通のインジェクションのためのコード。Detachは苦労した。

あとはframe_wrapper()に好きな処理を書いていけば良い。

  • どうやって描画するか
    • 東方が元から使っているDirectXを利用させてもらう。

DirectXについては
動的リンクで呼び出している関数へのフック、DirectXへのフック その2
または
MSDNなどで調べてください。

DirectXで描画関数を使うにはIDirect3DDeviceインタフェースであるBeginSceneやらを使う。
これはIDirect3DDeviceの仮想関数テーブルからアクセス出来る。
IDirect3DDeviceはグローバル変数に入っていたのでそこから拝借。

lpVtbl = (int *)**(pplpVtbl);

BeginScene = (HRESULT(__stdcall *)(int))*(lpVtbl+34);
EndScene = (HRESULT(__stdcall *)(int))*(lpVtbl+35);
Clear = (HRESULT(__stdcall *)(int, DWORD, int *, DWORD, D3DCOLOR, float, DWORD))*(lpVtbl+36);
Present = (HRESULT(__stdcall *)(int, DWORD, DWORD, DWORD, DWORD))*(lpVtbl+15);
DrawPrimitiveUP = (HRESULT(__stdcall *)(int, DWORD, DWORD, vertex*, DWORD))*(lpVtbl+72);
SetVertexShader = (HRESULT(__stdcall *)(int, int))*(lpVtbl+76);
GetVertexShader = (HRESULT(__stdcall *)(int, int *))*(lpVtbl+77);

これはattach()内に書いた。

あとはframe_wrapper()にこれらを利用して座標を描画する関数(collision_detection())を書くだけ。

void collision_detection(myship ms, vector<enemy>venemy, vector<etama>vetama){
	int pFVF;
	char buf[256];
	int i, j;
	int xl = 384;
	int yl = 448;

	//当たり判定の描画
	GetVertexShader(_this, &pFVF);

	SetVertexShader(_this, FVF_VERTEX);
	BeginScene(_this);
	
	for(i=0; i<venemy.size(); i++){
		for(j=0; j<4; j++){
			vertexTbl[j].z = 0.0;
			vertexTbl[j].color = D3DCOLOR_XRGB(255, 0, 0);
			vertexTbl[j].rhw = 1.0;
		}
		int dx = 32;
		int dy = 16;
		vertexTbl[0].x = venemy[i].x-venemy[i].dx/2+32;
		vertexTbl[0].y = venemy[i].y-venemy[i].dy/2+16;
		vertexTbl[1].x = venemy[i].x-venemy[i].dx/2+32;
		vertexTbl[1].y = venemy[i].y+venemy[i].dy/2+16;
		vertexTbl[2].x = venemy[i].x+venemy[i].dx/2+32;
		vertexTbl[2].y = venemy[i].y+venemy[i].dy/2+16;
		vertexTbl[3].x = venemy[i].x+venemy[i].dx/2+32;
		vertexTbl[3].y = venemy[i].y-venemy[i].dy/2+16;

		if(dx < vertexTbl[0].x && vertexTbl[2].x < dx+xl && dy < vertexTbl[0].y && vertexTbl[2].y < dy+yl)
			DrawPrimitiveUP(_this, D3DPT_TRIANGLEFAN, 2, vertexTbl, sizeof(vertex));
	}

	for(i=0; i<vetama.size(); i++){
		for(j=0; j<4; j++){
			vertexTbl[j].z = 0.0;
			vertexTbl[j].color = D3DCOLOR_XRGB(0, 0, 255);
			vertexTbl[j].rhw = 1.0;
		}
		int dx = 32;
		int dy = 16;
		vertexTbl[0].x = vetama[i].x-vetama[i].dx/2+32;
		vertexTbl[0].y = vetama[i].y-vetama[i].dy/2+16;
		vertexTbl[1].x = vetama[i].x-vetama[i].dx/2+32;
		vertexTbl[1].y = vetama[i].y+vetama[i].dy/2+16;
		vertexTbl[2].x = vetama[i].x+vetama[i].dx/2+32;
		vertexTbl[2].y = vetama[i].y+vetama[i].dy/2+16;
		vertexTbl[3].x = vetama[i].x+vetama[i].dx/2+32;
		vertexTbl[3].y = vetama[i].y-vetama[i].dy/2+16;

		if(dx < vertexTbl[0].x && vertexTbl[2].x < dx+xl && dy < vertexTbl[0].y && vertexTbl[2].y < dy+yl)
			DrawPrimitiveUP(_this, D3DPT_TRIANGLEFAN, 2, vertexTbl, sizeof(vertex));
	}

	vertex my_vertex[4];
	for(j=0; j<4; j++){
		my_vertex[j].z = 0.0;
		vertexTbl[j].color = D3DCOLOR_XRGB(0, 255, 0);
		vertexTbl[j].rhw = 1.0;
	}
	int dx = 32;
	int dy = 16;
	vertexTbl[0].x = ms.x1+dx;
	vertexTbl[0].y = ms.y1+dy;
	vertexTbl[1].x = ms.x1+dx;
	vertexTbl[1].y = ms.y2+dy;
	vertexTbl[2].x = ms.x2+dx;
	vertexTbl[2].y = ms.y2+dy;
	vertexTbl[3].x = ms.x2+dx;
	vertexTbl[3].y = ms.y1+dy;

	DrawPrimitiveUP(_this, D3DPT_TRIANGLEFAN, 2, vertexTbl, sizeof(vertex));

	EndScene(_this);
	SetVertexShader(_this, pFVF);

}

frame_wrapper()で敵の座標やらをvectorに入れてその後にcollision_detection()を呼び出している。
もうちょっとスタイリッシュに出来る感じが溢れてる。

これを実行するとこんな感じに。

やったぜ。

おわり

目が悪い人にしか見えない画像を復元してみるよ!Part2

いつかの記事http://d.hatena.ne.jp/aki33524/20120310/1331384580の続編

import Image
import math
from sets import Set

def show_rem():
    for i in range(len(rem_list)):
        print rem_list[i]
         
def rem_noise(point):
    r, g, b = im.getpixel(point)
    euclid = math.sqrt(r**2 + g**2 + b**2)
    if(euclid < 200 ):
        rem_list.append(point)
        dump1.putpixel(point, (255, 0, 0))
        dump2.putpixel(point, (255, 255, 255))

def num_of_NP((x, y)):
    sum = 0
    for i in (-1, 0, 1):
        for j in (-1, 0, 1):
            if (x+i, y+j) in set_rem_list:
                sum += 1
    return sum

def adj_color(point):
    r, g, b = im.getpixel(point)
    r = 255-(255-r)*10
    g = 255-(255-g)*10
    b = 255-(255-b)*10
    dump2.putpixel(point, (r, g, b))

def interpolation((x, y)):
    num = 0
    if 0<x and x+1<w and 0<y and y+1<h:
        list1 = []        
        for i in (-1, 0, 1):
            for j in (-1, 0, 1):
                if not (x+i, y+j) in set_rem_list:
                    num += 1
                    list1.append((x+i, y+j))
    if 1<num:
        r = 0
        g = 0
        b = 0
        for point in list1:
            r += dump2.getpixel(point)[0]
            g += dump2.getpixel(point)[1]
            b += dump2.getpixel(point)[2]
        rem_list.remove((x, y))
        dump2.putpixel((x, y), (r/num, g/num, b/num))
    
'''''''''''''''''''''''
Entory POINT
'''''''''''''''''''''''
rem_list = []
tmp_list = []
im = Image.open("youjo.bmp", 'r')
w, h = im.size
dump1 = Image.new("RGB", im.size)
dump2 = im.copy()
dump3 = im.copy()

'''
Remove noise and adjust
'''
for i in range(w):
    for j in range(h):
        rem_noise((i, j))
        adj_color((i, j))
'''
Remove noise 2
'''
set_rem_list = Set(rem_list)
for i in range(1, w-1):
    for j in range(1, h-1):
        point = (i, j)
        if num_of_NP(point) > 2:
            rem_list.append(point)
            dump1.putpixel(point, (255, 0, 0))
            dump2.putpixel(point, (255, 255, 255))
'''
for point in rem_list:
    dump2.putpixel(point, (255, 255, 0))
'''
            
set_rem_list = Set(rem_list)   
while 2000<len(rem_list):
    for point in rem_list:
        interpolation(point)        
    set_rem_list = Set(rem_list)
    print "len(rem_list):",len(rem_list)
print "end"
       
dump1.save("dump1.bmp")
dump2.save("dump2.bmp")

前回に比べてかなりソースが見やすくなったと思う。
このソースを書いてる時に、setsの存在を知って心が折れたので更新する。

dump1.bmp

dump2.bmp

さて、このプログラムListを使って色々しているが、非常に効率が悪い。実行終了まで一時間ほど掛かる。
かなりいい加減な作りだったりして考えたアルゴリズムになってない途中段階な感じ。
結局まだ未完なんで早いところPart3作りたい。

前に作ったのがこれ。

見比べると多少綺麗になった気もする

マインスイーパー解析

方針。
まずマインスイーパの爆弾位置を保存しているメモリアドレスを探す。地味にこれが大変。0x1005360に展開されてることが分かった。

その次にこの領域のメモリを他のプログラムから取得してもうちょっと見やすくしたかった。

参考にしたサイト。
http://www.mpcforum.com/showthread.php?251308-Tutorial-Basic-C-Game-Hacking-(Memory-Editing)
http://d.hatena.ne.jp/peryaudo/20100516/1273998518
以下ソースはほぼ上のサイトの丸パクリ

#include <windows.h>
#include <stdio.h>

int main() 
{
	HWND hWnd = FindWindow(0, "マインスイーパ");
  	if(hWnd == 0)
	{
    		MessageBox(0, "Error cannot find window.", "Error", MB_OK|MB_ICONERROR);
  	} 
	else 
	{
    		DWORD proccess_ID;
    		GetWindowThreadProcessId(hWnd, &proccess_ID);
    		HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, proccess_ID);
    		if(!hProcess)
		{
      			MessageBox(0, "Could not open the process!", "Error!", MB_OK|MB_ICONERROR);
    		} 
		else 
		{	unsigned char data[24*32];
     		 	DWORD datasize = sizeof(data);
      			if(ReadProcessMemory(hProcess, (LPVOID)0x1005360, data, datasize, NULL))
			{	
				int counter = 0;
				for(int i=0; i<24*32; i++){
						
						if(data[i] == 0x10){
							counter++;
							if(counter%2)
								printf("\n");
						}
						if(counter%2){
							if(data[i] == 0x8F)
								printf("B ");
							else if(data[i] == 0x0F)
								printf("%0X ", data[i]);							
						}						
					}
					MessageBox(NULL, "ReadProcessMemory worked.", "Success", MB_OK + MB_ICONINFORMATION);					
					
      			} 
			else 
			{
        				MessageBox(NULL, "Error cannot ReadProcessMemory!", "Error", MB_OK + MB_ICONERROR);
      			}
      			CloseHandle(hProcess);
    		}

  	}
  	return 0;
}

こんな感じにカンニングしながら解ける

マインスイーパー解析Part2

爆弾の位置を変更したりしたかった。

#include <windows.h>
#include <stdio.h>

int main() 
{
	HWND hWnd = FindWindow(0, "マインスイーパ");
  	if(hWnd == 0){
    	MessageBox(0, "Error cannot find window.", "Error", MB_OK|MB_ICONERROR);
  	}else{
    	DWORD proccess_ID;
    	GetWindowThreadProcessId(hWnd, &proccess_ID);
    	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, proccess_ID);
		unsigned char data[24*32];
		bool flag; //flagがtrueなら書き換えする
		
		printf("爆弾の位置のみを表示するなら0、位置の変更をしたいならそれ以外を入力---");
		scanf("%d", &flag);
     	
		//爆弾の数諸々の取得
		int bomb_num, w, h;
		ReadProcessMemory(hProcess, (void *)0x1005330, &bomb_num, sizeof(int), NULL);
		ReadProcessMemory(hProcess, (void *)0x1005334, &w, sizeof(int), NULL);
		ReadProcessMemory(hProcess, (void *)0x1005338, &h, sizeof(int), NULL);

		printf("Wideth:%d \nHeigh:%d \nBomb:%d\n", w, h, bomb_num);

    	if(!hProcess){
      		MessageBox(0, "Could not open the process!", "Error!", MB_OK|MB_ICONERROR);
    	}else{
			if(ReadProcessMemory(hProcess, (LPVOID)0x1005360, data, sizeof(data), NULL)){
				
				//爆弾読み込み兼爆弾除去				
				for(int i=0; i<h; i++){
					for(int j=0; j<w; j++){
					//爆弾だったら何もない状態に書き換える
						int offset = i*32 + j+1;
						if(data[offset] == 0x8F){
							char hoge = 0x0F;
							printf("B");
							if(flag){
							if(!WriteProcessMemory(hProcess, (void *)(0x1005360+offset), &hoge, sizeof(hoge), NULL))
								MessageBox(NULL, "Error cannot WriteProcessMemory!", "Error", MB_OK + MB_ICONERROR);
							}
						}else if(data[offset] == 0x0F){
							printf("0");
						}
					}
					printf("\n");
				}
				MessageBox(NULL, "ReadProcessMemory worked.", "Success", MB_OK + MB_ICONINFORMATION);

				//全て上に爆弾を移動させる
				if(flag){
				int set_bomb = 0;
				for(int i=0; i<h; i++){
					for(int j=0; j<w; j++){
						int offset = i*32 + j + 1;
						if(set_bomb < bomb_num){
							char hoge = 0x8F;
							if(!WriteProcessMemory(hProcess, (void *)(0x1005360 + offset), &hoge, sizeof(hoge), NULL))
							MessageBox(NULL, "Error cannot WriteProcessMemory!", "Error", MB_OK + MB_ICONERROR);
							set_bomb++;
						}
					}
				}
				MessageBox(NULL, "WriteProcessMemory worked.", "Success", MB_OK + MB_ICONINFORMATION);
				}
      		}else{
        		MessageBox(NULL, "Error cannot ReadProcessMemory!", "Error", MB_OK + MB_ICONERROR);
      		}
      		CloseHandle(hProcess);
    	}	
  	}
  	return 0;
}

前回のソースを継承して色々付け足したりした。コメントつけたから見やすいね!!

こんな感じになる

爆弾位置を変えなければもちろんそのままの位置でカンニングしながら解ける。

TwitterのGIFアイコン作ってみるよ!

TwitterでGIFアイコン使えるのは知っていたのだが、なかなか機会が無かったので思い立ったが吉日的にやってみた。

まず、動画をフレーム単位で読み込んだ。これはWindowsでAlt+PrintScreenでちまちましました。
ストパン一期EDより。


この二枚で2フレーム間です。
通常アニメは24fps/secですが、二枚連続同じ画像が続くので実質12/sec
今回抜き出した枚数は25枚

適当にサーニャが写ってるところだけ抜き出す。エイラなんていなかった。
ついでに背景も消した。

import Image

location = "/home/username/sanyaGIF"

n = 25
for i in range(n):
    box3 = (280+i*7, 100, 280+250+i*7, 100+250)
    im = Image.open("%s/input/%02d.png" %(location,i+1))
    im2 = im.crop(box3)
    width, height = im2.size
    
    for j in range(width):
        for k in range(height):
            point = (j, k)
            rgb = im2.getpixel(point)
            if rgb[0] + rgb[1] < rgb[2]*1.55:
                rgb = (255, 255, 255)
                im2.putpixel(point, rgb)
    im2.save("%s/output/%02d.png" %(location, i+1))

出力されたものをフリーのGIF作成ソフトに突っ込む


実はこれ、最初はこんな感じだった。

背景に影響されて輪郭が変色したため、残すのは不自然だった。

そこで上のように背景判断をキツ目にしたが、今度は輪郭が消えてこっちも不自然になってしまった。

そこで輪郭を作ろうと、一回り大きなサーニャのシルエットを作って、そこに元の画像を重ねるアプローチで行った。

import Image

location = "/home/username/sanyaGIF"

def resize(im, num):
    wideth ,height = im.size
    for i in range(wideth):
        for j in range(height):
            point = (i, j)
            rgb = im.getpixel(point)
            if rgb[0]!=255 or rgb[1]!=255 or rgb[2]!=255:
                rgb = (0, 0, 0)
                im.putpixel(point, rgb)
    scale = 1.1
    size = (int(wideth*scale), int(height*scale))
    im2 = im.resize(size)
    im2.save("%s/output2/%02d.png" %(location, num+1))
    
def paste(num):
    im  = Image.open("%s/output/%02d.png"  % (location, num+1))
    im2 = Image.open("%s/output2/%02d.png" % (location, num+1))
    
    wideth, height = im.size
    wideth2, height2 = im2.size
    box = (int((wideth2-wideth)/2), int((height2 - height)/2), int((wideth2-wideth)/2)+wideth, int((height2 - height)/2)+height)
    
    for i in range(wideth):
        for j in range(height):
            point = (int((wideth2-wideth)/2)+i, int((height2-height)/2)+j)
            #point = (i+50, j+50)
            if (255, 255, 255) != im.getpixel((i, j)):
                im2.putpixel(point, im.getpixel((i, j)))
    im3 = im2.crop(box)
    im3.save("%s/output3/%02d.png" % (location, num +1))
    #im2.save("%s/output3/%02d.png" % (location, num +1))
   
n = 25
for i in range(n):
    im = Image.open("%s/output/%02d.png" % (location, i+1))
    resize(im, i)
    
for i in range(n):
    paste(i)
    print i
    
print "end"

出来た結果がこちら

そもそものアプローチを間違えたことに気づく。これはこれでいいが、望んでいたものとは違う

今度はエッジ抽出して輪郭を見つけ出そうとするはじめからそうしろよ的アプローチで行った

import Image

location = "/home/username/sanyaGIF"

def edge(num):
    im = Image.open("%s/output/%02d.png" %(location, num+1))
    im2 = Image.new('RGB', im.size, (255, 255, 255))
    w, h = im.size
    
    for i in range(w):
        for j in range(h):
            if 1 < i < w-2:
                rgb = im.getpixel((i, j))
                if rgb[0]==255 and rgb[1]==255 and rgb[2]==255:
                    if im.getpixel((i-1, j)) != im.getpixel((i, j))or im.getpixel((i, j))!= im.getpixel((i+1, j)):
                        im2.putpixel((i, j), (0, 0, 0))
    for i in range(w):
        for j in range(h):
            if 1 < j < h-2:
                rgb = im.getpixel((i, j))
                if rgb[0]==255 and rgb[1]==255 and rgb[2]==255:
                    if im.getpixel((i, j-1)) != im.getpixel((i, j))or im.getpixel((i, j)) != im.getpixel((i, j+1)):
                        im2.putpixel((i, j), (0, 0, 0))
         
    im2.save("%s/output4/%02d.png" % (location, num+1))
 
def blur(num):
    im = Image.open("%s/output/%02d.png" %(location, num+1))
    im2 = Image.open("%s/output4/%02d.png" %(location, num+1))  
    w, h = im.size
    
    for i in range(w):
        for j in range(h):
            point = (i, j)
            if im2.getpixel(point) == (0, 0, 0):
                im.putpixel(point,im2.getpixel(point))
                
    for i in range(1, w-1):
        for j in range(1, h-1):
            point = (i, j)
            if im2.getpixel(point) == (0, 0, 0):
                rgb_sum = [0, 0, 0]
                for l in range(-1, 2):
                    for m in range(-1, 2):
                        point2= (i+l, j+m)
                        rgb_sum[0] += im.getpixel(point2)[0]
                        rgb_sum[1] += im.getpixel(point2)[1]
                        rgb_sum[2] += im.getpixel(point2)[2]
                im.putpixel(point, (rgb_sum[0]/9, rgb_sum[1]/9, rgb_sum[2]/9))
    
    im3 = im.resize((250, 250))
                
    im3.save("%s/output5/%02d.png" % (location, num+1))

def mask(num):
    im = Image.open("%s/output5/%02d.png" % (location, num+1))
    w, h = im.size
    
    for i in range(w):
        for j in range(h):
            if im.getpixel((i, j)) == (255, 255, 255):
                im.putpixel((i, j), (64, 174, 255))
                
    im.save("%s/output5/%02d.png" % (location, num+1))
              
n=25

for i in range(n):
    edge(i)
    print i

for i in range(n):
    blur(i)
    print i
for i in range(n):
    mask(i)
    print i
print "Finish"

抽出されたエッジがこちら

あとは繋ぎ目が自然になるように適当に出力されたファイルを並べ替えたりコマ数を落としたりした。これで完成!

磁力線を書いてみるよ!

学校で磁力線についてやったのでプログラムで書いてみる。

教科書曰く、磁力線とは「ちょっとずつ試験電荷を移動させた時の軌跡」らしい。
なら、位置ベクトルを作って、ちょっとずつ移動させた後に、その座標における新しいベクトルを取得すればいいんじゃね。
という方針でやってみた。

#define _USE_MATH_DEFINES
#include <stdio.h>
#include <math.h>

//極座標構造体
typedef struct{
	int mark;
	double x_coordinate, y_coordinate;
} pole;

//ベクトル構造体
typedef struct{
	int mark;	//markが0なら[+]、1なら[-]	
	double x_coordinate, y_coordinate;
	double x_vector, y_vector;
} vector;

//ベクトル構造体のメンバー表示
void vector_show(vector test){
	printf("-----\n");
	printf("%d\n", test.mark);
	printf("%f, %f\n", test.x_coordinate, test.y_coordinate);
	printf("%f, %f\n", test.x_vector, test.y_vector);
	printf("-----\n");
}

//次のベクトル取得
vector func(vector vec, pole pole1, pole pole2){
	vector ret;
	//vector_show(vec);
	double hoge = pow(pow(vec.x_vector, 2) + pow(vec.y_vector, 2), 0.5);
	//printf("%f\n", hoge);
	
	ret.x_coordinate = vec.x_coordinate + vec.x_vector / hoge / 10;
	ret.y_coordinate = vec.y_coordinate + vec.y_vector / hoge / 10;
	
	double hoge1 = pow(vec.x_coordinate - pole1.x_coordinate, 2) + pow(vec.y_coordinate - pole1.y_coordinate, 2);
	double hoge2 = pow(vec.x_coordinate - pole2.x_coordinate, 2) + pow(vec.y_coordinate - pole2.y_coordinate, 2);
	//printf("%f, %f\n", hoge1, hoge2);

	double x[2], y[2];

	x[0] = (vec.x_coordinate - pole1.x_coordinate) / hoge1;
	y[0] = (vec.y_coordinate - pole1.y_coordinate) / hoge1;
	x[1] = -1 * (vec.x_coordinate - pole2.x_coordinate) / hoge2;
	y[1] = -1 * (vec.y_coordinate - pole2.y_coordinate) / hoge2;
		
	ret.x_vector = x[0] + x[1];
	ret.y_vector = y[0] + y[1];

	ret.mark = 0;
	
	return ret;
}

int main(){
	pole pole1, pole2;
	FILE *fp = fopen("output.txt", "w");
	
	pole1.mark = 0;
	pole1.x_coordinate = 100;
	pole1.y_coordinate = 100;

	pole2.mark = 1;
	pole2.x_coordinate = 200;
	pole2.y_coordinate = 100;
	
	vector test;
	test.mark = 0;
	
	int n = 100;
	for(int i=0; i<n; i++){
		
		double x = cos(2*M_PI * i / n);
		double y = sin(2*M_PI * i / n);
		
		test.x_vector = x;
		test.y_vector = y;

		test.x_coordinate = pole1.x_coordinate + x;
		test.y_coordinate = pole1.y_coordinate + y;

		//printf("%f %f\n", test.x_vector, test.y_vector);

		double hoge = pow(pole2.x_coordinate - test.x_coordinate, 2) + pow(pole2.y_coordinate - test.y_coordinate, 2);
		
		
		while(50<test.x_coordinate && test.x_coordinate<250 && 0<test.y_coordinate && test.y_coordinate<200 && 1 < hoge){
			hoge =  pow(pole2.x_coordinate - test.x_coordinate, 2) + pow(pole2.y_coordinate - test.y_coordinate, 2);
			fprintf(fp, "%f %f\n", test.x_coordinate, test.y_coordinate);
			test = func(test, pole1, pole2);
		}		
	}

	fclose(fp);
	return 0;
}

簡単に説明すると、Pole1とPole2の2つの極を用意し、Pole1からのベクトルを演算していく感じ。
ここで問題なのは、Pole1からのベクトルしか演算できておらず、Pole2からの磁力線は無視してしまっていることである。

出力されたファイルをgnuplotに投げたのがこれ

なかなか美しいですね!

先述した通りPole2(右側の極、一応プログラム内では負のパラメータをもたせてるはず。)からの磁力線は演算されていない。
極や電荷に正負のパラメータを持たせてあるのは、汎用的な物を作ろうとして諦めた残骸ですね。

というわけで、このプログラムもまだ未完成なので、早く完成させたいものですね。

追記
画像整理してたら綺麗な形のが出てきた。いつ作ったのか覚えてない悲しみ

目が悪い人にしか見えない画像を復元してみるよ!

少し前にTwitter2chで出回ってた「目が悪い人にしか見えない画像」を復元してみるよ!


これが元画像。一見どうやって復元するんだこんなの!?となるかもしれないが、色情報がちゃんと残っている。GIMPで輝度と彩度をいじるとこんな感じになる。

という訳でPythonでプログラムを書く。

import Image

def get_color(__x, __y):
    color_list = []
    
    for i in range(3):
        for j in range(3):
            if 0<=__x+j-1<x and 0<=__y+i-1<y:
                if a[(__y+i-1)*x + __x+j-1] == 'false':
                    if  j != i:
                        color_list.append(imgout3.getpixel((__x+j-1, __y+i-1)))               
    _color = [0, 0, 0]
    for i in range(len(color_list)):
        _color[0] += color_list[i][0]
        _color[1] += color_list[i][1]
        _color[2] += color_list[i][2]
    if len(color_list) > 0:
        _color[0] /= len(color_list)
        _color[1] /= len(color_list)
        _color[2] /= len(color_list)
    else:
        _color = imgout3.getpixel((__x, __y))
        
    imgout3.putpixel((__x, __y), (_color[0], _color[1], _color[2]))
    return _color

def func(i):
    _point = {'x':i%x, 'y':(i-i%x)/x}
    if(a[i] == 'true'):
        _func(_point['x'], _point['y'])
       
def _func(_x, _y):
    print _x, _y
    __x = _x
    __y = _y
    ___x = _x
    ___y = _y
    
    hoge = x*_y+_x
    _hoge = x*_y+_x
    _sum = 0
    __sum = 0
    
    while a[hoge] == 'true':
        _sum += 1
        if  -1 <= __x < x-1 and 1 <= __y < y+1:
            __x += 1        
            __y -= 1
            hoge = x*__y+__x                 
        else:
            break
    color = [get_color(__x, __y)]
    while a[_hoge] == 'true':
        __sum += 1
        if  1 <= ___x < x+1 and -1 <= ___y < y-1:
            ___x -= 1
            ___y += 1        
            _hoge = x*___y+___x
        else:
            break
    
    color.append(get_color(___x, ___y))
    _r = color[1][0]+((color[0][0]-color[1][0])/(_sum+__sum))*__sum
    _g = color[1][1]+((color[0][1]-color[1][1])/(_sum+__sum))*__sum
    _b = color[1][2]+((color[0][2]-color[1][2])/(_sum+__sum))*__sum
    
    imgout3.putpixel((_x, _y), (_r, _g, _b))
           
im = Image.open("/home/youjo/youjo.bmp", 'r')
print im.format, im.size, im.mode
_tuple = im.size
print _tuple[0], _tuple[1]
point = {'x': _tuple[0], 'y': _tuple[1]}
x = point['x']
y = point['y']

imgout = Image.new("RGB", im.size)
imgout2 = Image.new("RGB", im.size, (255, 255, 255))
a = []

for i in range(y):
    for j in range(x):
        rgb = list(im.getpixel((j, i)))
        r = rgb[0]
        g = rgb[1]
        b = rgb[2]
        _r = 255-(255-r)*10
        _g = 255-(255-g)*10
        _b = 255-(255-b)*10
        
        imgout.putpixel((j, i), (_r, _g, _b))
       
        if r < 230 and g < 230 and b < 230:
            r = 255
            g = 255
            b = 255
            rgb = (255, 255, 255)
            imgout.putpixel((j, i), rgb)
            imgout2.putpixel((j, i), (0, 0, 0))                   
            a.append('true')
        else:
            a.append('false')        
    
imgout.save("/home/youjo/youjo_dump.bmp")
imgout2.save("/home/youjo/youjo_dump2.bmp")

imgout3 = Image.open("/home/youjo/youjo_dump.bmp")
for i in range(len(a)):
    func(i)
imgout3.save("/home/youjo/youjo_dump3.bmp")

ノリで書いたので分かりやすさも何もない酷いソースですね。
そのうち、改良版のソースを載せる予定なので許してください…
出力されるファイルが以下。

youjo_dump.bmp

youjo_dump2.bmp

youjo_dump3.bmp

このプログラムでは斜線を除いた部分を線間補正をグラーデーションで掛けている。あと、ノイズ除去も同時にやってる。

グラデーションを掛けてないバージョンはこれ

正直、グラデーション掛けた方と大差無いですね(´・ω・`)
目標は一見違和感がないレベルに!
ではまたー!