批量配置活動圖框
可多次上傳,新圖框會累加入清單,不影響已有圖框
點擊或拖曳多個圖框至此處
建議解析度:1024 x 768 (4:3比例)
沒有現成圖框?可用 AI 生成!先到「拍貼圖框設計師 GEM」依說明生成想要的圖框,再回到下方「去背工具」去背後匯入使用。
開啟 AI 圖框生成 GEM當前載入圖框
0 張
雲端同步狀態
未設定 GAS
將下載的網頁傳至使用者 iPad 或佈署至 Github Pages 即可拍照
圖框管理與合成大預覽
滑入縮圖顯示刪除鈕圖框清單:
請先於左側選取並上傳圖框圖片
上傳圖片開始去背
純瀏覽器 Canvas 技術,所有運算皆在本機執行,不上傳任何內容。
目前工具提示
累計上傳總人次
0 人
今日收到照片
0 張
系統同步狀況
等待同步指令中...
已接收之使用者作品快照
| 上傳時間 | 姓名 | 作品快照預覽 | 操作動作 |
|---|---|---|---|
| 點擊右上方「同步拍貼照資料夾」按鈕即可撈取雲端硬碟內的使用者作品 | |||
Google Apps Script (GAS) 連線密鑰設定
雲端後端 GAS 原始碼 (v4.0 - 原圖存雲端硬碟)
請在 Google 試算表點選「擴充功能」→「Apps Script」,貼上本代碼:(圖片將以原圖存入試算表同路徑的「拍貼照」資料夾,不再寫入試算表)
/**
* 剛好拍貼趣 - 雲端後端控制 GAS 腳本 (v4.1)
* 學生端以 no-cors 上傳「原圖 PNG」,後端存入試算表同路徑的「拍貼照」
* 資料夾並設為「知道連結者可檢視」;分享連結改以 JSONP 回傳,
* 以解決從本機 file:// 開啟時無法讀取回應的 CORS 限制。不再寫入試算表。
*/
// 取得(或建立)與本試算表同一層的「拍貼照」資料夾
function getPhotoFolder() {
var ssId = SpreadsheetApp.getActiveSpreadsheet().getId();
var parents = DriveApp.getFileById(ssId).getParents();
var parent = parents.hasNext() ? parents.next() : DriveApp.getRootFolder();
var found = parent.getFoldersByName("拍貼照");
return found.hasNext() ? found.next() : parent.createFolder("拍貼照");
}
// 學生端上傳圖片:存檔、設定分享、把分享連結暫存到 Cache 供 JSONP 取回
function doPost(e) {
var lock = LockService.getScriptLock();
var output = ContentService.createTextOutput().setMimeType(ContentService.MimeType.JSON);
try {
lock.waitLock(20000);
var data = JSON.parse(e.postData.contents);
var folder = getPhotoFolder();
var b64 = (data.photoData || "").replace(/^data:image\/\w+;base64,/, "");
var bytes = Utilities.base64Decode(b64);
var name = (data.studentName || "匿名學生").replace(/[\/\\]/g, "_");
var stamp = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "yyyyMMdd_HHmmss");
var file = folder.createFile(Utilities.newBlob(bytes, "image/png", name + "_" + stamp + ".png"));
file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
if (data.uploadId) {
CacheService.getScriptCache().put("url_" + data.uploadId, file.getUrl(), 600);
}
return output.setContent(JSON.stringify({ status: "success", fileUrl: file.getUrl() }));
} catch (err) {
return output.setContent(JSON.stringify({ status: "error", message: err.toString() }));
} finally { lock.releaseLock(); }
}
// JSONP 端點:
// ?action=geturl&uploadId=XXX&callback=cb → 回傳該次上傳的分享連結
// ?callback=cb → 回傳「拍貼照」資料夾內所有圖片(教師端)
function doGet(e) {
var p = (e && e.parameter) ? e.parameter : {};
var payload;
if (p.action === "geturl") {
var url = CacheService.getScriptCache().get("url_" + p.uploadId);
payload = url ? { status: "success", fileUrl: url } : { status: "pending" };
} else {
payload = listPhotos();
}
var json = JSON.stringify(payload);
if (p.callback) {
return ContentService.createTextOutput(p.callback + "(" + json + ")")
.setMimeType(ContentService.MimeType.JAVASCRIPT);
}
return ContentService.createTextOutput(json).setMimeType(ContentService.MimeType.JSON);
}
// 列出「拍貼照」資料夾內所有圖片(供教師端預覽)
function listPhotos() {
var folder = getPhotoFolder();
var files = folder.getFiles();
var records = [];
while (files.hasNext()) {
var f = files.next();
var base = f.getName().replace(/\.png$/i, "");
records.push({
timestamp: Utilities.formatDate(f.getDateCreated(), Session.getScriptTimeZone(), "yyyy/MM/dd HH:mm:ss"),
studentName: base.split("_")[0],
photoData: "https://drive.google.com/thumbnail?id=" + f.getId() + "&sz=w1000",
fileUrl: f.getUrl()
});
}
records.sort(function(a, b) { return a.timestamp > b.timestamp ? -1 : 1; });
return records;
}