adsense



통짜 이미지를 타일로 만들어주는 간이툴 '오버랩'

간단히 말해서

이런 그림(image)이 있다고 하면

그림 내에서 중복되는 영역을 정리해서

이렇게 타일 이미지(Tiled image)로 만들어 줍니다


필요해서 다시 꺼내든 김에 공개해 봅니다.

13년전에 피쳐폰용 게임에 쓰려고 만든거라 8비트 bmp파일(윈도우 포맷)만 다룰 수 있습니다. (투명 지원 x)
출력되는 타일은 무조건 16열 16행으로 만들어지고, 타일 갯수가 256개를 넘어가면 생성 실패합니다.
(소스 메시지 중에 '8비트 또는 24비트만 지원'한다고 써놨는데.. 어 아마 24비트 안될겁니다.. ^^; 그렇게 만들려다가 메시지만 그리 써놓고..)


사용법
java overlap [소스파일(확장자떼고)] 타일가로픽셀 타일세로픽셀 [C]

마지막에 C를 붙여주면 타일 이미지를 하나로 묶인 것 외에 한 장 한 장 별도의 bmp로 생성합니다.

실행예


그리고 실행 결과물에 .map라는 파일이 하나 더 생성되는데, 타일로 원래 이미지를 재구성하기 위한 정보입니다.
바이너리 데이타로 파일 형식은,
가로 4바이트 세로 4바이트,
그리고 타일 하나에 2바이트씩
가로 방향으로 나열됩니다.

요즘 만들면 투명 있는 24비트 png 지원하고 결과물도 json이나 xml로 뽑겠지만.. 개량을 할지는?


생성된 타일 이미지는 가장 일반적인 맵 타일 툴인 Tiled에서도 쓸 수 있습니다.
빈 공간은 포토샵 같은데서 잘라내고 사용하는게 좋겠네요.



다운로드
overlap.zip


import java.io.*;
import java.lang.*;
 
class overlap
{
static byte img[],
tile_data[][],
tile_restore[][];
static String fname;
static int
offset,
index2,
tile_w, tile_h,
width, height,
tw_no, th_no,
bpp, // bits per pixel
tile_checksum[],
tile_map[][],
tile_map2[];
 
public static void main(String[] args)
{
if(args.length<3) {
System.out.println("java overlap [sourcefile] [tile_width] [tile_height] (create tile option 'C')");
return;
}
tile_w=Integer.parseInt(args[1]);
tile_h=Integer.parseInt(args[2]);
System.out.println("OVERLAPPING Start... : source file ["+args[0]+"] tile_width="+args[1]+", tile_height="+args[2]);
try
{
RandomAccessFile raf = new RandomAccessFile(args[0]+".bmp", "r");
img = new byte[(int)raf.length()];
raf.read(img);
raf.close();
}
catch (Exception e)
{
System.out.println("Wrong file or file not found : "+args[0]);
return;
}
fname=args[0];
offset=getInt(img, 10);
width=getInt(img, 18);
height=getInt(img, 22);
bpp=getInt(img, 28);
tw_no=width/tile_w;
th_no=height/tile_h;
if(width%tile_w!=0) tw_no++;
if(height%tile_h!=0) th_no++;
 
System.out.println("이미지 가로 너비 : "+width);
System.out.println("이미지 세로 높이 : "+height);
System.out.println("타일맵 가로 너비 : "+tw_no);
System.out.println("타일맵 세로 높이 : "+th_no);
System.out.println("그림 시작 위치 : "+offset);
System.out.println(""+getInt(img, 28)+"bit/pixel");
if(bpp!=8&&bpp!=24) {
System.out.println("bmp 포맷은 8비트 컬러 또는 24비트 컬러만을 지원합니다");
return;
}
//tile_w=tile_w*(bpp/8);
 
/*System.out.print("계속하시려면 [y]를 입력하세요...:");
try
{
int a=System.in.read();
if(a!='y') return;
}
catch (Exception e)
{
}*/

 
System.out.print("Processing");
 
tile_data = new byte[tw_no*th_no][tile_w*tile_h*(bpp/8)];
tile_restore = new byte[tw_no*th_no][tile_w*tile_h*(bpp/8)];
tile_checksum = new int[tw_no*th_no];
tile_map = new int[tw_no][th_no];
tile_map2 = new int[tw_no*th_no];
 
takeTileData();
System.out.print("d");
initTileMap();
System.out.print("i");
makeTileMap();
System.out.print("o");
System.out.println("");
System.out.println("중복정리가 끝났습니다. 정리된 타일 수 :"+index2);
writeTotalTile();
System.out.println("통합 타일을 생성합니다.");
if(args.length>3) {
if(args[3].equals("C")||args[3].equals("c")) {
System.out.println("타일을 생성합니다.");
writeTile();
System.out.println("");
}
}
System.out.println("맵배열을 생성합니다.");
writeMap();
System.out.println("타일 생성이 끝났습니다");
}
static void takeTileData() {
// 그림을 타일로 분할하여 배열에 저장
//int index=tw_no*th_no-1; // bmp 파일 특성상 뒤에서부터 저장되므로..
int index=0;
for(int i=0;i<th_no;i++) {
for(int j=0;j<tw_no;j++) {
tile_checksum[index]=getTileData(index, j, i);
index++;
}
System.out.print(".");
}
}
static int getTileData(int index, int x, int y) {
// 그림을 타일로 분할하여 배열에 저장
int checksum=0;
byte pixel=0;
for(int i=0;i<tile_h;i++) {
for(int j=0;j<tile_w;j++) {
for(int k=0;k<(bpp/8);k++) {
if(x*tile_w+j>=width||y*tile_h+i>=height) pixel=0;
else pixel=img[offset+(x*tile_w+j)*(bpp/8)+(y*tile_h+i)*width*(bpp/8)+k];
//else pixel=img[offset+y*tile_h+i*width*(bpp/8)+(x*tile_w+j)];
tile_data[index][i*tile_w+j+k]=pixel;
checksum+=pixel;
}
}
}
return checksum;
}
static void initTileMap() {
// 타일 맵을 초기화
int init_value=th_no*tw_no+1;
for(int i=0;i<th_no;i++)
for(int j=0;j<tw_no;j++) {
tile_map[j][i]=init_value;
tile_map2[j+i*tw_no]=init_value;
}
}
 
static void makeTileMap() {
int index=0;
index2=0;
for(index=0;index<th_no*tw_no;index++) {
if(tile_map[index%tw_no][index/tw_no]<th_no*tw_no+1) continue; // 이미 타일 넘버가 할당된 부분
tile_map[index%tw_no][index/tw_no]=index2;
tile_map2[index]=index2;
tile_restore[index2]=tile_data[index];
for(int i=index+1;i<tw_no*th_no;i++) {
if(checkOver(index, i)) {
tile_map[i%tw_no][i/tw_no]=index2;
tile_map2[i]=index2;
}
}
index2++;
}
}
 
static boolean checkOver(int index, int target) {
if(tile_checksum[index]!=tile_checksum[target]) return false;
for(int i=0;i<tile_h;i++) {
for(int j=0;j<tile_w;j++) {
if(tile_data[index][i*tile_w+j]!=tile_data[target][i*tile_w+j]) return false;
}
}
return true;
}
 
static void writeTile() {
int id2=0;
byte header[] = new byte[offset];
byte bmpdata[] = new byte[offset+tile_w*tile_h*(bpp/8)+2];
System.arraycopy(img, 0, header, 0, offset); // 헤더 생성
Int2Byte(bmpdata.length, header, 2);
Int2Byte(tile_w, header, 18);
Int2Byte(tile_h, header, 22);
Int2Byte(tile_w*tile_h*(bpp/8), header, 34);
for(int i=0;i<index2;i++) {
//if(index2>3) id2=3; else id2=index2;
//for(int i=0;i<id2;i++) {
System.arraycopy(header, 0, bmpdata, 0, offset); // 헤더 카피
System.arraycopy(tile_restore[i], 0, bmpdata, offset, tile_w*tile_h*(bpp/8)); // 본체 카피
bmpdata[bmpdata.length-2]=0;
bmpdata[bmpdata.length-1]=0; // 최후미 2바이트 추가
writeFile(i, bmpdata);
System.out.print(".");
}
}
static void writeFile(int index, byte bmpdata[]) {
String fn=fname+"tile_"+index;
if(index==65535) fn=fname+"_total";
try
{
FileOutputStream os = new FileOutputStream(fn+".bmp");
os.write(bmpdata, 0, bmpdata.length);
os.flush();
if(os!=null) {
os.close();
os=null;
}
}
catch (Exception e)
{
}
}
 
static void writeTotalTile() {
if(index2>256) {
System.out.println("타일 수가 256개를 초과하여 통합 타일을 생성하지 않습니다.");
return;
}
int id2=0, off_tile, offx, offy;
byte header[] = new byte[offset];
byte bmpdata[] = new byte[offset+tile_w*16*tile_h*16*(bpp/8)+2];
for(int i=0;i<bmpdata.length;i++) bmpdata[i]=(byte)0xff;
System.arraycopy(img, 0, header, 0, offset); // 헤더 생성
Int2Byte(bmpdata.length, header, 2);
Int2Byte(tile_w*16, header, 18);
Int2Byte(tile_h*16, header, 22);
Int2Byte(tile_w*16*tile_h*16*(bpp/8), header, 34);
System.arraycopy(header, 0, bmpdata, 0, offset); // 헤더 카피
for(int i=0;i<16;i++) {
for(int j=0;j<16;j++) {
offy=(15-i)*tile_h*16*tile_h;
//offx=(15-j)*tile_w;
//offy=i*tile_h*16*tile_h;
offx=j*tile_w;
off_tile=0;
for(int k=0;k<tile_h;k++) {
for(int l=0;l<tile_w;l++) {
bmpdata[offset+offx+offy+l+k*tile_w*16]=tile_restore[id2][off_tile];
off_tile++;
}
}
id2++;
if(id2==index2) break;
}
if(id2==index2) break;
}
writeFile(65535, bmpdata);
}
 
static void writeMap() {
byte fs[] = new byte[tw_no*th_no*2+8];
Int2Byte(tw_no, fs, 0);
Int2Byte(th_no, fs, 4);
/*for(int i=0;i<th_no*tw_no;i++) {
fs[8+i*2]=(byte)tile_map2[i];
fs[8+i*2+1]=0;//attr
}*/

int id2=tw_no*th_no-1;
for(int i=0;i<th_no;i++) {
for(int j=0;j<tw_no;j++) {
fs[8+id2*2]=(byte)tile_map[tw_no-j-1][i];
fs[8+id2*2+1]=0;//attr
id2--;
}
}
try
{
FileOutputStream os = new FileOutputStream(fname+".map");
os.write(fs, 0, fs.length);
os.flush();
if(os!=null) {
os.close();
os=null;
}
}
catch (Exception e)
{
}
}
 
 
public static void Int2Byte(int value, byte[] data, int idx) {
data[idx+3] = (byte)(value>>24);
data[idx+2] = (byte)(value>>16);
data[idx+1] = (byte)(value>>8);
data[idx+0] = (byte)(value);
}
public static int getInt(byte data[], int idx) {
return (
((data[idx+3]&0xff)<<24)|
((data[idx+2]&0xff)<<16)|
((data[idx+1]&0xff)<<8)|
(data[idx]&0xff)
);
}
public static int getInt_(byte data[], int idx) {
return (
((data[idx]&0xff)<<24)+
((data[++idx]&0xff)<<16)+
((data[++idx]&0xff)<<8)+
(data[++idx]&0xff)
);
}
}
 



덧글

댓글 입력 영역


Books

Geek라이프

메가 드라이브 퍼펙트 카탈로그
마에다 히로유키 저/조기현 역

미소녀 일러스트 테크닉
B-은하, pen스케, 카와이 저/정유진 역

핵심강좌! Cocos2d-x
이재환 저

피규어의 교과서 레진 키트 & 도색 입문 편
후지타 시게토시 저/김정규 역
예스24 | 애드온2
일본서적 전문사이트 NEPIC