Skip to content

Commit

Permalink
find icons of 10 gacha
Browse files Browse the repository at this point in the history
  • Loading branch information
uhyo committed Feb 2, 2018
1 parent 90df092 commit 05d29b2
Show file tree
Hide file tree
Showing 14 changed files with 461 additions and 2 deletions.
2 changes: 2 additions & 0 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ const PRODUCTION = process.env.NODE_ENV === 'production';
entry: path.join(__dirname, TS_DIST_LIB, 'index.js'),
output: {
path: path.join(__dirname, DIST_LIB),
// XXX
publicPath: '/dist/',
filename: BUNDLE_NAME,
},
}
Expand Down
30 changes: 30 additions & 0 deletions html/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>きららファンタジアのガチャ画像をまとめるやつ</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
body {
margin: 0;
font-family: sans-serif;
}
h1 {
font-size: 1.4rem;
}
p {
margin: 0;
}
input {
box-sizing: border-box;
padding: 0.3em;
}
</style>
</head>
<body>
<h1>きららファンタジアのガチャ画像をまとめるやつ</h1>
<div id="app"></div>
<script src="../dist/bundle.js"></script>
</body>
</html>

28 changes: 28 additions & 0 deletions lib/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {
Component,
h,
} from 'preact';

import {
FileSelect,
} from './file-select';

import {
main,
} from './logic/main';

export class App extends Component<{}, {}> {
public render() {
const fileHandler = (files: FileList)=>{
main(files);
};
return <div>
<p>
<FileSelect
label='ガチャ画像を選択'
onSelect={fileHandler}
/>
</p>
</div>;
}
}
51 changes: 51 additions & 0 deletions lib/file-select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {
Component,
h,
} from 'preact';

export interface IPropFileSelect {
label: string;
onSelect(files: FileList): void;
}

/**
* File select button.
*/
export class FileSelect extends Component<IPropFileSelect, {}> {
public render() {
const {
label,
} = this.props;

return <button onClick={this.handleClick.bind(this)}>
{label}
</button>;
}
protected handleClick(): void {
// File select button is clicked.
const {
onSelect,
} = this.props;

const input = document.createElement('input');
input.type = 'file';
input.multiple = true;
input.style.display = 'none';

input.addEventListener('change', ()=>{
if (input.files != null) {
onSelect(input.files);
}
}, false);

document.body.appendChild(input);

setTimeout(()=>{
input.click();
setTimeout(()=>{
document.body.removeChild(input);
}, 500);
}, 0);

}
}
Empty file removed lib/index.ts
Empty file.
13 changes: 13 additions & 0 deletions lib/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {
h,
render,
} from 'preact';

import {
App,
} from './app';

document.addEventListener('DOMContentLoaded', ()=>{
const app = document.getElementById('app');
render(<App />, app);
});
180 changes: 180 additions & 0 deletions lib/logic/find-icons-worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import {
IIcon,
IWorkerResult,
} from './find-icons';

// TypeScript workaround
const ctx: Worker = self as any;

// background color of the gacha board.
const bg = {
blue: 0xff,
green: 0xd7,
red: 0xaa,
};

// state of scanner.
const enum State {
Start,
UpBorder,
Gacha10_1,
CenterMargin,
}

ctx.onmessage = (e)=> {
// Receive an ImageData.
const {
data,
width,
height,
} = e.data as ImageData;

// index of buffer.
let idx = 0;
// states.
let s = State.Start;
let y1: number | undefined;
let xs: number[] | undefined;
let y2: number | undefined;
let sizex: number | undefined;
let sizey: number | undefined;

// Scan each line.
wholeloop: for (let y = 0; y < height; y++) {
let bgcnt = 0;
for (let x = 0; x < width; x++) {
const red = data[idx];
const green = data[idx+1];
const blue = data[idx+2];

if (red === bg.red && green === bg.green && blue === bg.blue) {
// this is bg!
bgcnt++;
}
idx += 4;
}
console.log(bgcnt);
// State transition
switch (s) {
case State.Start: {
if (bgcnt >= 650) {
// Found a board.
s = State.UpBorder;
}
break;
}
case State.UpBorder: {
if (80 <= bgcnt && bgcnt <= 150) {
// Found a 10 gacha.
// Start of first line.
y1 = y - 1;
s = State.Gacha10_1;

// Get start position of each boxes.
const linestart = 4 * width * (y+10);
[xs, sizex] = detectX(data.subarray(linestart, linestart + 4 * width), width, 5);
}
break;
}
case State.Gacha10_1: {
if (bgcnt >= 650) {
// End of gacha1.
sizey = y - y1!;
s = State.CenterMargin;
}
break;
}
case State.CenterMargin: {
if (80 <= bgcnt && bgcnt <= 150) {
// Start of second line.
y2 = y - 1;
// Job is done!
break wholeloop;
}
break;
}
}
}

// End of scanning.
const result: IIcon[] = [];
if (sizex != null && sizey != null) {
if (y1 != null && xs != null) {
for (const x of xs) {
result.push({
height: sizey,
width: sizex,
x,
y: y1,
});
}
}
if (y2 != null && xs != null) {
for (const x of xs) {
result.push({
height: sizey,
width: sizex,
x,
y: y2,
});
}
}
}

const answer: IWorkerResult = {
data: e.data,
result,
};

ctx.postMessage(answer, [data.buffer]);
};

/**
* Detect each box of gacha icon.
*/
function detectX(data: Uint8ClampedArray, width: number, max: number): [number[], number] {
const result = [];
let curx: number | undefined;
let sizex: number | undefined;

let state = 0;
let bgcont = 0;

for (let x = 0; x < width; x++) {
const idx = x * 4;
const red = data[idx];
const green = data[idx+1];
const blue = data[idx+2];
const bgf = red === bg.red && green === bg.green && blue === bg.blue;

console.log(bgf, bgcont);
if (bgf) {
// count continues background pixels.
bgcont++;
if (bgcont === 1 && state === 1 && curx != null) {
// it's the end of the first box.
sizex = x - curx;
state = 2;
}
} else {
if (bgcont >= 12) {
// yes, it's after a true background region
if (result.length < max) {
console.log('found!', x, state);
result.push(x);
}
if (state === 0) {
curx = x;
state = 1;
}
}
bgcont = 0;

}
}
if (sizex != null) {
return [result, sizex];
} else {
throw new Error('Something went wrong');
}
}
56 changes: 56 additions & 0 deletions lib/logic/find-icons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import FindIconWorker from 'worker-loader!./find-icons-worker';

export interface IIcon {
x: number;
y: number;
width: number;
height: number;
}

export interface IFindIconsResult {
image: HTMLCanvasElement;
result: IIcon[];
}

export interface IWorkerResult {
data: ImageData;
result: IIcon[];
}

/**
* Find icons from an image.
*/
export function findIcons(image: HTMLImageElement): Promise<IFindIconsResult> {
// Invoke a worker.
const worker = new FindIconWorker();

// Render image into a canvas.
const canvas = document.createElement('canvas');
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
const ctx = canvas.getContext('2d');
if (ctx == null) {
throw new Error('Could not get a context');
}
ctx.drawImage(image, 0, 0);
// Retrieve an ImageData.
const data = ctx.getImageData(0, 0, canvas.width, canvas.height);

// Send this data to the worker.
worker.postMessage(data, [data.data.buffer]);

return new Promise((resolve, reject)=>{
worker.onmessage = (e)=> {
const answer: IWorkerResult = e.data;
const {
result,
} = answer;
console.log(result);
resolve({
image: canvas,
result,
});
};
worker.onerror = reject;
});
}
Loading

0 comments on commit 05d29b2

Please sign in to comment.