2014年5月29日 星期四

拼圖生產器(三)

還是想不到解決黑線的方法;只好用最耗時的方式處理。是這樣的:

1) 掃瞄已裁出來的圖片
2) 當像素是綠色,亦即是要保留的像素時,檢查其上下左右的像素是否為黑
3) 若是黑色的話,則把它收成青色
4) 直至掃瞄完成
5) 之後重新掃瞄一次
6) 把綠色及青色像素,按照片的像素保留
7) 其餘的以透明色代替

以下是拼圖生產器的 PHP 代碼:
<?php
//----------------------------------------------------------------------------------------
//  Jigsaw Puzzle Creator: Create Puzzle Mask
//----------------------------------------------------------------------------------------
//  Written by Pacess HO
//  Copyright 2014 Hotaru Production.  All rights reserved.
//----------------------------------------------------------------------------------------

header("Content-type: image/png");

//----------------------------------------------------------------------------------------
$directory = "./puzzles/";
$filename = "Sita.jpg";
$width = 800;
$height = 600;

$columns = 4;
$rows = 4;

//----------------------------------------------------------------------------------------
if (isset($_GET["image"]))  {
 $filename = $_GET["image"];
 $filePath = $directory.$filename;

 //  Get image dimension
 list($width, $height) = getimagesize($filePath);
}

if (isset($_GET["width"]))  {$width = $_GET["width"];}
if (isset($_GET["height"]))  {$height = $_GET["height"];}

if (isset($_GET["column"]))  {$columns = $_GET["column"];}
if (isset($_GET["row"]))  {$rows = $_GET["row"];}

//----------------------------------------------------------------------------------------
$pieceWidth = $width/$columns;
$pieceHeight = $height/$rows;

$maskImage = imagecreatetruecolor($width, $height);
$oddColor = imagecolorallocate($maskImage, 255, 250, 200);
$evenColor = imagecolorallocate($maskImage, 255, 200, 200);
$borderColor = imagecolorallocate($maskImage, 0, 0, 100);

//----------------------------------------------------------------------------------------
//  Draw grid blocks
$angle = rand();
$sinWidth = $pieceWidth*0.02;
$sinHeight = $pieceHeight*0.02;

$ratio = 160;
$angleStep = 180/$pieceWidth;
for ($y=1; $y<$rows; $y++)  {
 for ($x=0; $x<$width; $x++)  {

  $radians = ($angle%360)*0.0174532925;

  $drawX = $x;
  $drawY = ($y*$pieceHeight)+(sin($radians)*$sinHeight);
  imageline($maskImage, $drawX, $drawY, $drawX, $drawY, $borderColor);

  $angle += $angleStep;
 }
}

$angleStep = 180/$pieceHeight;
for ($x=1; $x<$columns; $x++)  {
 for ($y=0; $y<$height; $y++)  {

  $radians = ($angle%360)*0.0174532925;

  $drawX = ($x*$pieceWidth)+(sin($radians)*$sinWidth);
  $drawY = $y;
  imageline($maskImage, $drawX, $drawY, $drawX, $drawY, $borderColor);

  $angle += $angleStep;
 }
}

//  Fill blocks
for ($y=0; $y<$rows; $y++)  {
 for ($x=0; $x<$columns; $x++)  {

  $drawX = ($x*$pieceWidth)+($pieceWidth*0.5);
  $drawY = ($y*$pieceHeight)+($pieceHeight*0.5);

  $shouldDraw = ($x+$y)&1;
  if ($shouldDraw == 0)  {
   imagefill($maskImage, $drawX, $drawY, $evenColor);
  }  else  {
   imagefill($maskImage, $drawX, $drawY, $oddColor);
  }
 }
}

//----------------------------------------------------------------------------------------
//  Draw male/female
$diameterX = $pieceWidth*0.3;
$diameterY = $pieceHeight*0.3;
$offsetX = ($diameterX*0.3);
$offsetY = ($diameterY*0.3);
for ($y=0; $y<$rows; $y++)  {
 for ($x=0; $x<$columns; $x++)  {

  $shouldDraw = ($x+$y)&1;

  if ($x != 0)  {
   $value = $pieceHeight/20;
   $randomValue = rand(-$value, $value);
   $drawY = ($y*$pieceHeight)+($pieceHeight*0.5)+$randomValue;
   if ($shouldDraw == 0)  {
    $drawX = ($x*$pieceWidth)-$offsetX;
   }  else  {
    $drawX = ($x*$pieceWidth)+$offsetX;
   }
   imagefilledellipse($maskImage, $drawX, $drawY, $diameterX, $diameterY, $evenColor);
  }

  if ($y != 0)  {
   $value = $pieceWidth/20;
   $randomValue = rand(-$value, $value);
   $drawX = ($x*$pieceWidth)+($pieceWidth*0.5)+$randomValue;
   if ($shouldDraw == 0)  {
    $drawY = ($y*$pieceHeight)+$offsetY;
   }  else  {
    $drawY = ($y*$pieceHeight)-$offsetY;
   }
   imagefilledellipse($maskImage, $drawX, $drawY, $diameterX, $diameterY, $oddColor);
  }
 }
}

//----------------------------------------------------------------------------------------
//  Save mask as PNG
$pathParts = pathinfo($filename);
$outputPath = $pathParts["filename"]."_mask.png";
$saveResult = imagepng($maskImage, $directory.$outputPath);

//----------------------------------------------------------------------------------------
//  Create puzzle pieces
mkdir($directory.$pathParts["filename"], 0777, true);
$photoImage = imagecreatefromjpeg($filePath);

$top = 0;
for ($y=0; $y<$rows; $y++)  {

 $left = 0;
 for ($x=0; $x<$columns; $x++)  {

  $cropX1 = $left-$diameterX;
  $cropX2 = $left+$diameterX+$pieceWidth;
  $cropY1 = $top-$diameterY;
  $cropY2 = $top+$diameterY+$pieceHeight;

  if ($cropX1 < 0)  {$cropX1 = 0;}
  if ($cropY1 < 0)  {$cropY1 = 0;}

  //  Copy image to a new buffer
  $cropWidth = $cropX2-$cropX1+1;
  $cropHeight = $cropY2-$cropY1+1;
  $cropImage = imagecreatetruecolor($cropWidth, $cropHeight);

  imagealphablending($cropImage, false);
  imagesavealpha($cropImage, true);
  $transparentColor = imagecolorallocatealpha($cropImage, 0, 0, 0, 127);

  imagecopy($cropImage, $maskImage, 0, 0, $cropX1, $cropY1, $cropWidth, $cropHeight);

  //  Fill piece to cropping color
  $drawX = ($cropWidth*0.5);
  $drawY = ($cropHeight*0.5);
  $cropColor = imagecolorallocate($cropImage, 255, 0, 255);
  imagefill($cropImage, $drawX, $drawY, $cropColor);

  //  Fill the black border
  $sourceY = $cropY1;
  $cropBorderColor = imagecolorallocate($cropImage, 0, 255, 255);
  for ($pieceY=0; $pieceY<$cropHeight; $pieceY++)  {

   $sourceX = $cropX1;
   for ($pieceX=0; $pieceX<$cropWidth; $pieceX++)  {

    $maskColor = imagecolorat($cropImage, $pieceX, $pieceY);
    if ($maskColor != 0xff00ff)  {continue;}

    if ($pieceX > 0)  {
     $maskColor = imagecolorat($cropImage, $pieceX-1, $pieceY);
     if ($maskColor == 0x000064)  {imagesetpixel($cropImage, $pieceX-1, $pieceY, $cropBorderColor);}
    }

    if ($pieceY < ($cropWidth-1))  {
     $maskColor = imagecolorat($cropImage, $pieceX+1, $pieceY);
     if ($maskColor == 0x000064)  {imagesetpixel($cropImage, $pieceX+1, $pieceY, $cropBorderColor);}
    }

    if ($pieceY > 0)  {
     $maskColor = imagecolorat($cropImage, $pieceX, $pieceY-1);
     if ($maskColor == 0x000064)  {imagesetpixel($cropImage, $pieceX, $pieceY-1, $cropBorderColor);}
    }

    if ($pieceY < ($cropHeight-1))  {
     $maskColor = imagecolorat($cropImage, $pieceX, $pieceY+1);
     if ($maskColor == 0x000064)  {imagesetpixel($cropImage, $pieceX, $pieceY+1, $cropBorderColor);}
    }
   }
  }

  //  Replace mask pixel by image pixel
  $sourceY = $cropY1;
  for ($pieceY=0; $pieceY<$cropHeight; $pieceY++)  {

   $sourceX = $cropX1;
   for ($pieceX=0; $pieceX<$cropWidth; $pieceX++)  {

    $maskColor = imagecolorat($cropImage, $pieceX, $pieceY);

    if ($maskColor == 0xff00ff || $maskColor == 0x00ffff)  {

     //  Body
     $color = imagecolorat($photoImage, $sourceX, $sourceY);
     imagesetpixel($cropImage, $pieceX, $pieceY, $color);
    }  else  {

     //  Transparent
     imagesetpixel($cropImage, $pieceX, $pieceY, $transparentColor);
    }

    $sourceX++;
   }
   $sourceY++;
  }
  imagepng($cropImage, $directory.$pathParts["filename"]."/".$pathParts["filename"]."_piece_".$x."_".$y.".png");
  imagedestroy($cropImage);

  $left += $pieceWidth;
 }
 $top += $pieceHeight;
}

imagedestroy($photoImage);
imagedestroy($maskImage);

//----------------------------------------------------------------------------------------
//  Upload error
$pieceCount = $rows*$columns;
$resultArray = array(
 "result"=>0,
 "saveResult"=>$saveResult,
 "outputPath"=>$outputPath,
 "pieceCount"=>$pieceCount);
echo(json_encode($resultArray));

?>

2014年5月28日 星期三

拼圖生產器(二)


繼續開發昨天的拼圖生產器。有了遮罩,下一步是把每一小塊分拆出來。我沒有想到好的方法,只有最愚蠢的。用 For...Loop 把每一小塊拷出來,用 Flood Fill 填上綠色。再讓程式把綠色的像素對照相片相同的位置,把那位置的像素挎回來;把非綠色的像素設定為透明。這樣就能生產出一塊塊拼圖小塊。

以下就是《拼圖生產器》輸出的成果,還有待改良:

2014年5月27日 星期二

拼圖生產器


有客戶找公司開發一套 Jigsaw 拼圖遊戲。圖畫的數目不多,可以由美術同事逐塊逐塊製作。但對於我來說,更希進能把它自動化。萬一圖案有甚麼要修改的地方,按個鍵便能生成新的拼圖圖檔;減少人手出錯的機會之餘,又能按需要修改。如把 5x4 的拼圖改為 6x5。於是花了點時間研究。受到某人的影響,先找找有沒有 Framework 或教學之類的資源。沒有發現。便著手我最愛的自行研究。深入研究簡單編程過後,基本上已得到拼圖的遮罩,之後便是由遮罩生成一塊一塊的拼圖,以及輸出圖案座標,好讓程式能把拼圖組合。

2014年5月26日 星期一

試閱版的漏洞(二)

之前的研究的應用都屬於同一個開發機構,當中還有一個財經雜誌 App 也有提供試閱版。有趣的是,之前試閱版的漏洞並不適用這個 App;事關在服務器傳回空的參數,自然試閱版的內容只會是預設的東西。拿更新用的網址,嘗試把目錄往上推看一看,服務器則會傳回最新雜誌參數。例如地址是 http://www.pacess.com/magazine/tryme/getContent.php 改成 http://www.pacess.com/magazine/getContent.php。在編程人員的角度想,兩者的格式應該會是一樣,以方便維護。若假設成立的話,用 Charles 等 HTTP Proxy 把 HTTP 請求的地址即時修改,便能成功顯示最新內容。

2014年5月23日 星期五

loginSuccess


今日研究了一個報紙 App。開啟後是登入畫面,要成功登入才會顯示當天的報章內容。利用《Snoop-it》得知當前顯示的是 WelcomeViewController。查看它的功能列表,很容易找到一個名為「loginSuccess」的功能。估計是判斷帳號有效時,呼叫這個功能跳到內容畫面。

這種編程設計十分常見,方便在不同的 ViewController 都能調用;而且維護起上來也較為方便。但若遇上《Snoop-it》的話,便很容易跳過檢查。所以我現時都不用這種設計;改為使用 NSNotification。所有通知都彈到 handleNotification 功能內處理。有人想突破缺口,先要找出通知的名稱及其相關變量,再模擬發出通知才能成功。花的功夫絕對比單擊「loginSuccess」來得上百倍。

2014年5月22日 星期四

試閱版的漏洞


最近用得多雜誌 App,發現它們都有「試閱版」給用戶參考。這個「試閱版」通常都是過氣了的期刊。在編程及營運的角度考慮,「試閱版」內容多數放在雲端,在有需要時才下載、有需要時才更新。就是因為這樣的設計,往往應用程式會向雲端取得當刻「試閱版」的資料,而往往正式版的內容也放在同一個地方。因此漏洞便會出現!只要利用 HTTP Proxy + Rewrite 把由雲端傳回應用程式的數據修改,便能達到試閱最新完全版的效果。雖然雲端上的 PDF 或 JPG 都一定被加密,能防止有人直接下載檔案。要解碼確實有一定的難度,但由 App 自己解讀就防不勝防。不同期刊的密碼,不見得一定不同。管理人也會偷懶。

要改善以上問題,首先要在不同期刊中使用不同密碼;其次是把「試閱版」的路徑分開,而且目錄名稱不帶有意思。

2014年5月21日 星期三

陳僖儀 U Magazine 原大圖



無意中發現了 Sita 陳僖儀在兩期 U Magazine 曾有報導;又是豐富《陳僖儀補完計劃》的時間。找到兩期的相關加密 JPG 檔,可是無法解開,唯有利用上次的手法,直接把 TapDetectingImageView.UIImage 輸出成 PNG 格式。其實輸出 JPG 格式也沒有難度,只是原圖是 JPG,再加以 JPG 處理的話,便會再次失真。我希望保留最完美的版本,使用 PNG 格式就能滿足需要。

Rules
[20120329 -> [20130131
[0331 -> [0375
[18 -> [164
[24 -> [164

2014年5月19日 星期一

美化二維碼

Beauty QR Code Creator

看到人家把 QR Code 跟照片合併,變得色彩鮮豔;若配合得宜還能使商標及 QR Code 融合在一起。似乎很有趣,決定研究一下;看看能否用在舊客戶的 QR Code 項目!

花了三小時設計,完成了 Javascript + PHP 的工具。拿了幾張 Sita 的照片做測試,全部都能成功解讀。不過,有部份圖片效果不理想,看不清照片的內容,有待改良。





2014年5月15日 星期四

Sita PDF Password


PDF 密碼原來收藏在 PSPDFDocument.password。我那五本 Sita 的雜誌 PDF 密碼終於找到了。

2014年5月14日 星期三

在 Raspberry Pi 上安裝 Kali Linux(二)


安裝好的《Kali Linux》1.0.6a 在進入 GUI 後,發現主選單的 Kali Linux 項目沒有出現。原來為了節省空間,特別是 Raspberry Pi 這試容量小配備少的機器,很多工具都沒有內建。使用者要針對自身的需要進行安裝。我使用的 SD 卡只有 16GB,安裝完《Kali Linux》後剩下 10GB 空間,要節省地用。選擇了安裝 3.5GB 的 KALI-LINIX-TOP10。在 Terminal 輸入 apt-get install kali-linux-top10 後等上兩小時才安裝完成。期間還有兩次需要手動設定的機會。完成後,順道以 apt-get upgrade 把元件升級。之後以 startx 進入系統便能看到 Kali Linux 選項。

其他的工具包資料:
http://www.kali.org/news/kali-linux-metapackages/

2014年5月13日 星期二

TED Talks: The new bionics


在看《TED Talks》時發現了這條影片,展示出最新的機械人技術用在非常有意義的層面。希望機械人技術能普及化,幫助更多的人。

2014年5月12日 星期一

Disney: Pixelbots


在 YouTube 發現了 Disney Research Hub 開發的 Pixelbots。透過多個 Pixelbots 拼成一幅幅的動畫,十分有創意。我一直都想開發大量連動的機械人,今次給了我一個非常吸引的點子。

2014年5月11日 星期日

在 Raspberry Pi 上安裝 Kali Linux


搞 Kali Linux 真的很耗時,單是在 MacBook Pro 上把 Kali Linux 寫到 SD 卡就花了兩個多小時。SD 卡是屬於 Class 2,似乎慢了點,於是買來一張 16GB 的 Class 10,結果是啟動快了一點點。家裡有其他 Class 10 的 Micro SD 於,可是啟動失敗,畫面沒有訊號,藍畫面。這個經驗話我知只有用原版大卡才沒有問題。可能是 Micro SD 是經過 USB 手指而出事。

安裝方法
  • Offensive Security 下載 Raspberry Pi 版的 Kali Linux
  • 解壓成 .img 檔
  • 在 Mac 上打開 Terminal 並以 su - 登入 Root
  • 跳轉到 .img 的目錄
  • 插入 SD 卡並在 Disk Utility 中 Unmount
  • 輸入 dd if=kali-linux-1.0.6a-rpi.img of=/dev/disk1 bs=512k 並拍一下 Return 鍵
  • 等待把 Kali Linux 影像寫到 SD 卡
  • 完成後在 Disk Utility 中退出 SD 卡
  • 插入 SD 卡到 Raspberry Pi 後接上電源
  • 待要求登入時,輸入 root 及密碼 toor
  • 之後要做的是 Partition 把擴展到全張卡,輸入 fdisk -u /dev/mmcblk0 進入 fdisk
  • 按 p 鍵顯示當刻 Partition 設定,記錄第二個 Partition
  • 按 d 刪除第 2 個 Partition
  • 按 n 建立新的 primary partition
  • 按預設值輸入或拍 Enter 設定
  • 完成後按 w 把新設定寫入
  • 輸入 reboot 重新開機
  • 同樣以 root / toor 登入
  • 輸入 resize2fs /dev/mmcblk0p2 套用新的 Partition 設定
  • 輸入 apt-get update 進行一次系統更新
  • 輸入 apt-get install xfce4 xfce4-goodies 安裝所需部件
  • 完成後輸入 startx 啟動圖像介面
  • 2014年5月10日 星期六

    Sita 陳僖儀雜誌原大圖(二)


    無意中看到 iPad 上有一個雜誌 App 還有上年 Sita 的報導。下載回來的是 .xjpg 檔案。相信是加密了的 JPG 內容。可是找不到解碼的方法。既然解不到就直接把記憶體儲存成 PNG 格式。

    在 Snoop-it 找出 OSReaderViewController 的地址。這個類有一個 pagingView 的變量,而 pagingView 裡有 pagingScrollView。那是 UIScrollView。查看 UIScrollView 內的 subviews 會發現有幾個 PhotoPage 的 Instance 存在。為何有幾個?是因為考慮到揭頁太快時不至於頁面突然彈出的處理。PhotoPage 就是頁面的 View。內有個名為 image 的 UIImageView,只要把 UIImageView 內的 UIImage 輸出成檔案就行。Cycript 最利害的是可以執行 Objective-C 的指令。把 PhotoPage 變成 Cycript 的變量後,調用 [UIImagePNGRepresentation(photoPage.image.image) writeToFile:@"/var/mobile/Applications/page.png" atomically:YES]; 搞定。

    2014年5月8日 星期四

    紅米 + OTG 線


    佛誕那天小米開賣,買了三顆大電、五顆小電、OTG 線。訂了四次電夾夾埋埋都有 30 顆,全都是替朋友訂;唯獨今次的 OTG 線是為自己。購買 OTG 線是因為早排有客人向我們查詢,想開發一套透過 OTG 線運行的 Android 應用。雖然在網上搜過資料,還是買一條回來。我不知深水埗賣 OTG 線要多少錢,反正我只有紅米一台 Android 手機,以 HK$29 購入原廠 OTG 線,接受得到。

    首先經 OTG 線接上 USB 手指,立即偵測到,打開「檔案管理」看到「外部 USB 儲存裝置」。跟著的都是正常不過的瀏覽檔案介面。接著試試連上滑鼠。十分有趣。滑鼠發光,屏幕上多了個游標,也能移來移去。不錯不錯。Android 真的是一個硬件友善的平台。未來的機械人大戰應該是她的天下。

    2014年5月7日 星期三

    陳僖儀 Classic 原大圖


    網友找到 Sita 在 Classic 雜誌網頁的圖片,問我有沒有方法取得。進入頁面是一個 Flash 造的揭頁程式,於是看看 HTML 的內容是甚麼。很簡單,各頁的高清路徑已經出現。後來想到用 chrome://cache 應該來得更簡單。一試發現只有縮圖的路徑。不過,只要加點想像力,縮圖路徑是可以變成高清的路徑。

    2014年5月6日 星期二

    大數據(二)

    最近跟客人及合作伙伴開會,他們都提到「大數據」。在他們的眼中,認為大數據很有用,可以從中得知使用者的習慣,從而作出預估,並推銷相關產品來增加消費。聽落很合乎邏輯。問題是「該如何做?」。當我這麼一問,大家都啞口了。以前在 Gameone 服務時,一位編程同事教了我一句說話:「只要你能說得出箇中邏輯,我便能把它轉化成代碼」。他所言甚是。至少我也做得到。

    大家啞口,是因為無法有系統地、有邏輯地解釋如何從數據中取中有用的資源。能說得出的,當中都隱藏了某些步驟。例如:「如果使用者是上班一族時就這樣這樣」、「如果使用者月入兩萬時就那樣那樣」、「如果使用者是健談時就這樣這樣」...。那「上班一族」、「月入」、「健談」是從哪裡得知?有些數據可讓使用者自行輸入,但有更大部份是得透過「Machine Learning」得來。對此,目前是毫無頭緒,唯有在 Coursera 及 Udacity 上學習學習,希望有朝一日能做出以上分析...。

    2014年5月2日 星期五

    Pernod Ricard: Share In Style


    每逢有新編程同事述職,我都要找些易上手的工作給他們。今次遇到老客戶再做 Facebook App,正好可以給新同事邊做邊學習。這個客戶的應用非常簡單,只有標題畫面及排行榜畫面。當中用到了 HTML + Javascript + JQuery + AJAX + PHP + MySQL。看似很多但都是很基本的東西;也能用在 iOS / Android 的開發上,是不錯的學習工作。今天,同事的第一個作品面世了,不知他的感覺如何?

    2014年5月1日 星期四

    真心員工難求

    踏入五月便是開始支高薪的時間。支高薪的是編程部的同事。加薪幅度是 35%;而另一位,則相信是受前者影響而要求加薪 22%。一位是辭職而加薪挽留;另一位是出於公平原則,公司自動加薪。無獨有偶,兩者都討價還價,兩次的體驗都很不是味兒。我基本上處於被動狀態。要麼離開,要麼加到他們想要的薪酬。

    同事把握到機遇,乘著上升的風浪,跟公司要求加薪是自然不過的事。令我討厭的是大家知道公司的情況不好,在這刻沒肯少少的讓步一下;而且年初已經加薪,作為老闆,誠意可算是十足。如果公司是賺錢的話,加薪沒有問題,我老早已經把同事的利益放在第一位。我真的抱著有福同享的心態;問題是福未到難先來。在蝕錢的情況下,這樣的加薪幅度,就有如心藏流血的人還被插多幾刀。真的,當刻的感覺有如被人用刀威脅著,要麼拿錢出來,要麼找死。賓主關係何時變得這麼現實?

    當刻,我真的想由他們離開。因為關係已變成明買明賣,沒有心了。我有問過自己有沒有其他的選擇?選擇是有的。以他們最新的薪酬招聘,一定有很多人應徵。但招聘需時,新同事來到也要時間上手,無法立即有生產力。權衡考慮過後,只好跟自己說:「做生不如做熟,鬼叫你沒有他們不行」,接受吧!