零基礎的 PhotoShop CEP 6 開發(fā)教程 「 8 」API - 文件讀寫與二進制數(shù)據(jù)

《零基礎的 PhotoShop CEP 6 開發(fā)教程》系列目錄

「 0 」目錄
「 1 」配置開發(fā)環(huán)境
「 2 」CEP 文件結構
「 3 」CEP 的運行機制
「 4 」Hello World !
「 5 」事件(EVENTS)
「 6 」調用 JSX 并傳遞信息
「 7 」UI - HTML 開發(fā)的一些細節(jié)
「 8 」API - 文件讀寫與二進制數(shù)據(jù)
「 9 」簽名打包與 ZXPSignCmd
「 X 」CEP 更新到 6.1版了


這篇文章介紹的是 CEP 的文件的讀寫與二進制數(shù)據(jù)處理的相關內容。
首先要說明的是由于 CEP 的 JavaScript 運行在 Node.js 引擎上,所以能夠使用 Node.js 的 fs模塊(有關fs 可以參考 fs 模塊 - 《JavaScript 標準參考教程(alpha)》。)就能進行文件的讀寫了,如下面的例子,

var fs = require('fs'); //引入 fs 模型
fs.readFile("D:/autorun.inf",function(err,data){ console.log(data)}) //讀取文件并交給回調函數(shù)

Node.js 的文件讀寫操作完全能替代 CEP 的提供的 API,但還是推薦使用 CEP 的 API ,畢竟是官方標準,各宿主之間的兼容性更有保障。

此外后面還花了大篇幅講了 JavaScript 的二進制處理相關知識供參考。

Adobe 為 CEP 提供了自己的 API 來方便進行讀寫操作,實際上和使用 Node.js 的 fs 沒有什么區(qū)別,不過 CEP 的接口的方法都是同步的,更容易理解。
CEP 文件操作的方法放在 window.cep.fs 對象中。

文件路徑

這里要介紹 CEP 中的文件路徑。
CEP 中的路徑是 Unix 風格的 / 分隔,而不是 Windows 風格的 \\,但是由于 CEP 擴展可以同時在 Windows 和 OS X 中運行,所以就有了跨平臺問題,這個后面會說如何解決跨平臺問題。

  • / 代表根目錄, CEP 中為 PhotoShop 安裝目錄的根目錄,比如 D:/
  • ./ 、. 代表當前目錄,同時如果沒有前綴也表示當前目錄,比如 cc 等同于 ./cc, CEP 中是 PS 安裝目錄 ,如 D:/PS/Adobe Photoshop CC 2015
  • ../、.. 代表上級目錄,可以連續(xù)使用: ../../

跨平臺路徑處理

由于 OSX 和 Windows 上路徑格式的不同,所以我們常常得對其處理,雖然 Node.js 和 CEP 提供的文件相關接口都是統(tǒng)一使用 Unix 風格的路徑格式,但難免要處理來自本地的路徑,要想簡單的處理路徑格式問題,可以使用 Node.js 自帶的 Path 模塊提供的功能。

var path = require('path'); //使用前先引入 path 模塊
path.join("foo", "bar");
  • 拼接路徑
    使用 path.join(); 來替代手動用 + 拼接路徑,在不同系統(tǒng)上自動使用相應分隔符(要注意的是 CEP 提供的接口無論在 Windows 還是 OS X 上都是用 /):
path.join("AAA", "fff"); // 取代 "AAA"+"/" +"fff"
//在  OS X  上:AAA/fff
//在 Windows 上: AAA\\fff
  • 路徑標準化
    如果你覺得使用 + 拼接路徑根據(jù)方便也無妨,path 還有一個路徑標準化功能,在最終使用路徑前用 path.normalize() 處理路徑就好了,路徑標準化還有去除路徑中無效字符的功能(比如 sd///ds 變成 sd/ds)。
path.normalize("Files/Adobe/CEP/");
//在  OS X  上:Files/Adobe/CEP/
//在 Windows 上: Files\Adobe\CEP\"
  • 獲得相對路徑的絕對路徑
    使用 path.resolve(); 可以得到如 ./ 這樣相對路徑的絕對路徑
   path.resolve("./");
  // "D:\PS\Adobe Photoshop CC 2015"

常用路徑:

*這里的 cs指 CSInterface 對象,即:var cs = new CSInterface();。

路徑 例子
process.execPath Node.js 引擎可執(zhí)行文件。 D:\PS\Adobe Photoshop CC 2015\Required\CEP\CEPHtmlEngine\CEPHtmlEngine.exe
__dirname 擴展所在目錄 C:/Program Files (x86)/Common Files/Adobe/CEP/extensions/fonTags
__filename 當前文件目錄 C:/Program Files (x86)/Common Files/Adobe/CEP/extensions/fonTags/index.html
cs.getSystemPath(SystemPath.USER_DATA) 系統(tǒng)用戶數(shù)據(jù)文件夾 C:/Users/語冰/AppData/Roaming
cs.getSystemPath(SystemPath.COMMON_FILES) 系統(tǒng)公共庫文件夾 C:/Program Files/Common Files
cs.getSystemPath(SystemPath.MY_DOCUMENTS) “我的文檔” C:/Users/不知語冰/Documents
cs.getSystemPath(SystemPath.HOST_APPLICATION) 宿主應用程序可執(zhí)行文件 D:/PS/Adobe Photoshop CC 2015/Photoshop.exe

讀取文件

window.cep.fs.readFile(路徑, 方式編碼) ,提供文件路徑,和文本編碼(默認按 UTF-8 處理),返回一個對象,其中

  • .err 存放返回錯誤信息
  • .data 以字符串形式存放讀取返回的數(shù)據(jù),
    var path = "D:/1.txt";
    var result = window.cep.fs.readFile(path);
    if (0 == result.err)// err 為 0 讀取成功
    {
        console.log(result.data);
    }
    else
    {
        console.log("讀取錯誤:" + result.err);// 失敗
    }

Base64 模式讀取

Base64 是一種把二進制數(shù)據(jù)以文本(64個可打印字符)形式表示的編碼方法。由于 CEP 的 readFile() 只能讀取文件后返回的 .data 只能是字符串,所以如果你要讀取一個非文本的二進制文件,比如一張圖片,就需要使用 Base64 模式讀取,這樣返回的.data中會是你讀取文件數(shù)據(jù)的 Base64 編碼后的字符串,否則讀取的數(shù)據(jù)會被強制裝換為字符串會丟失內容。

readFile() 第二個參數(shù)傳入 "Base64" 即可以 Base64 模式讀取。

    var path = "D:/1.txt"; 
    var result  = window.cep.fs.readFile(path ,"Base64");
    result.data //Base64 字符串: "77+977+977+977+977+977+977+977+977+977+977+977+977…v73v?v73vv73vv73vv73vv73vv73vv73vv73vv73vv73vv70="

對于讀取到的 Base64 的字符串,可以 CEP 提供了 cep.encoding.convertion 里的方法來進行轉換,

  • .b64_to_ascii(base64str) Base64 字符串 以 ascii 編碼轉換為字符串
  • .b64_to_utf8(base64str) Base64 字符串 以 UTF-8 編碼轉換為字符串
  • .b64_to_binary(base64str) Base64 字符串以二進制編碼轉換為字符串

另外還有反過來的方法:.ascii_to_b64(ascii).utf8_to_b64(str)、.binary_to_b64(binary)。
要注意的是 .b64_to_binary().binary_to_b64() 實際上是就是 window.atob()window.btoa(),而這 2 個方法返回的是字符串,而且是不支持 Unicode 字符的(比如中文),所以不要因為名字是 binary 就以為它能處理二進制數(shù)據(jù),它們是用來處理含不可傳輸?shù)?ASCII 控制字符的。

要處理其非文本的二進制數(shù)據(jù)的 Base64 ,可以使用 Node.js 的 Buffer(關于 Buffer 后面有說):

    var result  = window.cep.fs.readFile( "D:/1.txt","Base64");
    var buf = new Buffer (result.data, 'base64');

寫入文件

與 CEP 的 readFile() 一樣,寫入文件接受的數(shù)據(jù)也是字符串:

    var data = "文本內容";
    var result = window.cep.fs.writeFile( "D:/1.TXT", data);
    if (0 == result.err) {
        // 成功·
    }
    else {
         // 失敗
    }

要想對文件寫入二進制數(shù)據(jù),必須得使用 Base64 模式,并給 writeFile() Base64 字符串 下面這個例子展示的是讀取一個二進制文件,并原樣寫出的過程:

    var inf = window.cep.fs.readFile ("D:/A.ico", "Base64"); //以 Base64 模式讀入文件
    window.cep.fs.writeFile ("D:/B.ico", inf.data, "Base64");//以 Base64 模式寫出文件

新建目錄

window.cep.fs.makedir (path)
在指定位置新建文件夾

    var result = window.cep.fs.makedir(__dirname+"/"+"EEE");
    if (0 == result.err) {
        console.log("成功")
    }
    else {
        console.log("錯誤:" + result.err)
    }

刪除文件

window.cep.fs.deleteFile(path);
用法同上,注意只能刪除文件,不能刪除文件夾

重命名文件

rename(oldPath, newPath)
可以重命名文件和文件夾。

var result = window.cep.fs.rename(__dirname+"/EEE" ,__dirname+"/EEE2" );
    if (0 == result.err)
    {
       console.log("重命名成功");  
    }
    else
    {
        console.log("錯誤:" + result.err);
    }

讀取目錄中文件列表

window.cep.fs.readdir(path); 可以讀取一個文件夾中存放的文件和文件夾(只是一級)列表。
讀取的列表以文件名數(shù)組的形式存放在返回值的.data

    var result = window.cep.fs.readdir(__dirname );
    if (0 == result.err)
    {
       console.log( result.data);  
           // [".idea", "css", "CSXS", "EEE", "font", "img", "js", "jsx", "tem", ".debug", "1.TXT", "index.html"]
    }
    else
    {
        console.log("錯誤:" + result.err);
    }


判斷路徑是文件還是路徑

window.cep.fs.stat(path).data.isDirectory()window.cep.fs.stat(path).data.isFile() 可以檢測一個路徑是文件還是文件夾。

    var result = window.cep.fs.stat(__dirname + "/" + "EEE");
    if (0 == result.err)
    {
            if (result.data.isDirectory() == true)
            {
              console.log("這是個文件夾");
            }
            else if (result.data.isFile() == true)
            {
               console.log("這是個文件");
            }
    }
    else
    {
        console.log("錯誤:" + result.err)
    }

獲取文件最后一次被修改的時間

讀取 window.cep.fs.stat(path).data.mtime 可以獲取文件文件最后一次被修改的時間

設置文件權限

window.cep.fs.chmod (path, mode)
設置指定文件的權限,權限是用 Unix 風格的 chmod 數(shù)字表示權限。
在 Windows 上,只能設 0 為文件加上只讀屬性,777 取消只讀屬性。

打開、保存文件對話框

window.cep.fs.showOpenDialog (allowMultipleSelection, chooseDirectory, title, initialPath, fileTypes)
window.cep.fs.ShowOpenDialogEx (allowMultipleSelection, chooseDirectory, title, initialPath, fileTypes, friendlyFilePrefix, prompt)
window.cep.fs.showSaveDialogEx (title, initialPath, fileTypes, defaultName, friendlyFilePrefix, prompt, nameFieldLabel)

這個方法能彈出一個系統(tǒng)的文件選擇對話框,用來選擇或者保存文件,得到的文件以數(shù)組的形式存放在返回值的 .data 里。

參數(shù)的作用為:
showOpenDialog (允許多選, 選擇目錄模式, 標題, 初始路徑, 指定文件類型數(shù)組)
ShowOpenDialogEx (允許多選, 選擇目錄模式, 標題, 初始路徑, 指定文件類型數(shù)組, 文件類型說明, prompt消息)
showSaveDialogEx (標題, 初始, 文件類型, 默認名稱, 文件類型說明, prompt信息, 名稱字段標簽)

其中 prompt信息, 名稱字段標簽是 OSX 中才起作用的。

選擇目錄

彈出選擇目錄對話框,這時只有標題、初始路徑參數(shù)起作用。

   var result = window.cep.fs.showOpenDialog (true, true, "標題", "D:/", "")
   result.data
  //["E:/Qt_Project"]
選擇目錄模式
打開文件
var result = window.cep.fs.showOpenDialogEx (false, false, "標題", "D:/", ["txt","ico"] , "文件類型說明");
result;

打開文件對話框
保存文件
    result = window.cep.fs.showSaveDialogEx ("標題", "D:/", ["txt"], "默認名稱.TXT", "文件類型說明");
    if (0 == result.err)
    {
        if(result.data.length==0)
        {
           console.log("用戶放棄了保存");
        }
        else
        {
           console.log(result.data);
        }
     }
    else
    {
        console.log("錯誤:" + result.err)
    }

保存文件對話框

JavaScript 的二進制處理

JavaScript 當年起草的時候并沒有想到自己以后應用范圍會那么廣,所以一開始并沒有處理文件\二進制數(shù)據(jù)的功能,對文件\二進制數(shù)據(jù)的相關功能是后來一點點補增的,這讓 JavaScript 文件\二進制數(shù)據(jù)處理的知識相較于其他語言要更雜亂,更麻煩。

Blob 和 File 對象

Blob 對象是 ECMAScript 5 標準才引入的新內容,Blod 是一個存放二進制數(shù)據(jù)的容器,然而 Blob 對象成員屬性只有 2 個 :size 表示數(shù)據(jù)長度,type 表示數(shù)據(jù)的類型(mime type),并不能讀寫已經(jīng)放入 Blod 中的數(shù)據(jù),他大多數(shù)情況下的作用只是用來生成一個可以下載的文件(還有剪貼板、拖拽操作可能會用到):

var myBlob = new Blob(["這是文本"], { "type" : "text\/xml" }); //把數(shù)據(jù)裝入 Blob
var href = window.URL.createObjectURL(blob); //生成下載鏈接

File 對像繼承了 Blob 對象,并增加了 name,lastModifiedDate 等文件相關屬性,但依然和 Blob 一樣并不能讀寫已經(jīng)放入的數(shù)據(jù)。

ArrayBuffer 和 TypedArray、DataView 對象

ArrayBuffer

要對二進制數(shù)據(jù)讀寫,需要使用緩沖數(shù)組:ArrayBuffer ,這個對象實際上對應的就是傳統(tǒng)語言中的數(shù)組,即在創(chuàng)建申請所需長度的內存,數(shù)組內容存放在連續(xù)的內存中,并且長度不能動態(tài)增減。
在 JavaScript 中 ArrayBuffer 本身只有創(chuàng)建指定長度 ArrayBuffer 的功能,要對 ArrayBuffer 進行修改需要使用數(shù)據(jù)視圖對象 TypedArray 或者 DataView。

var buf = new ArrayBuffer(32); // 創(chuàng)建 32 字節(jié)長度的 ArrayBuffer

DataView

數(shù)據(jù)視圖要解決的問題是按何種數(shù)據(jù)類型去處理數(shù)據(jù),舉例來說就是一個字節(jié)(8位)是把他當成取值范圍 0~255 的無符號整數(shù)(Uint8) 還是取值范圍 -128~127 的帶符號整數(shù)(Int8)處理。 JavaScript 支持以下數(shù)據(jù)類型:

數(shù)據(jù)類型 字節(jié)長度 含義 對應的傳統(tǒng)語言類型
Int8 1 8 位帶符號整數(shù) signed char
Uint8 1 8 位無符號整數(shù) unsigned char
Uint8C 1 8 位無符號整數(shù)(自動過濾溢出) unsigned char
Int16 2 16 位帶符號整數(shù) short
Uint16 2 16 位無符號整數(shù) unsigned short
Int32 4 32 位帶符號整數(shù) int
Uint32 4 32 位無符號的整數(shù) unsigned int
Float32 4 32 位浮點數(shù) float
Float64 8 64 位浮點數(shù) double
一個 8 字節(jié)數(shù)據(jù)不同視圖下的處理單位

一個 DataView 只是一個視圖,并不存儲數(shù)據(jù),他指向一個 ArrayBuffer ,我們使用 DataView 的各種方法按想要的數(shù)據(jù)類型去讀寫他指向的 ArrayBuffer :

var buf = new ArrayBuffer(32); // 創(chuàng)建 32 字節(jié)長度的 ArrayBuffer
var dataView = new DataView(buf);// 以 buf 為數(shù)據(jù)創(chuàng)建視圖 dataView 
dataView.setUint8(3,0xff); //把 dataView 對應數(shù)據(jù)按 Uint8(無符號 8 位整數(shù) 0~255)處理,在第 4 位 Uint8 中寫入 0xff
dataView.getUint8(3); // 按 Uint8 讀取第 4 位。 返回 255(即 0xff)

DataView 有 3 個屬性:

  • buffer :指向的 ArrayBuffer;
  • byteOffset :數(shù)據(jù)偏移值
  • byteLength :數(shù)據(jù)長度

表示的是 DataView 可以操作 bufferbyteOffset 開始 byteLength 長度的數(shù)據(jù),其可以在創(chuàng)建對象時指定(也可修改):

var buf = new ArrayBuffer(8);// 創(chuàng)建 8字節(jié)長度的 ArrayBuffer
var dataView = new DataView(buf, 3, 2);// 以 buf 的第 4 位開始取 2 位長度的數(shù)據(jù)創(chuàng)建視圖 dataView 

默認 DataView 會以 ArrayBuffer 的所有數(shù)據(jù)創(chuàng)建視圖。

DataView 讀寫數(shù)據(jù)的 .getXXX ,和 setXXX 方法都是從指定字節(jié)位來讀取數(shù)據(jù)的,這意味著如果你要依次讀取 Uint16 類型(一個 Uint16 占 2 個字節(jié))的數(shù)據(jù),你要使用的是 .getUint16(0), .getUint16(2), .getUint16(4), .getUint16(6),需要你手動處理數(shù)據(jù)類型長度,這有靈活操作的好處,但通常這樣只會增加讓程序員手動處理數(shù)據(jù)類型長度的麻煩。所以如果我們不是為了更靈活自由的處理數(shù)據(jù)類型,或者指定字節(jié)序的話更常用 TypedArray (類型化數(shù)組)。

TypedArray

TypedArray 類型化數(shù)組,和 DataView 實際上的功能是一樣,都是為數(shù)據(jù)操作指定數(shù)據(jù)類型,不同的是 DataView 是拿到數(shù)據(jù)后進行可以按各種數(shù)據(jù)類型進行操作,而 TypedArray 是先按指定的數(shù)據(jù)類型拿到數(shù)據(jù),以后都按這種數(shù)據(jù)類型進行操作。這樣操作帶來的好處就是無需程序員手動處理數(shù)據(jù)類型長度,并且減少每次操作都指定數(shù)據(jù)類型的麻煩,并且 TypedArray 雖然也是對 ArrayBuffer 的封裝,但 TypedArray 可以和數(shù)組一樣用下標操的方式操作,編寫代碼和調試都更加方便。

TypedArray 實際上有一組對象,名字為 XXXArray 的形式,XXX 在前面 DataView 那張數(shù)據(jù)類型表上有。分別是:Int8Array、Uint8Array、Uint8ClampedArrayInt16Array、Uint16ArrayInt32Arra、Uint32Array、Float32Array、Float64Array。

var buf = new ArrayBuffer(8); // 創(chuàng)建 8 字節(jié)長度的 ArrayBuffer
var u8 = new Uint8Array(buf);// 以創(chuàng)建 buf 創(chuàng)建 TypedArray

//同 DataView 一樣 TypedArray 也可以只截取 ArrayBuffer 的一部分:
var u8_2 = new Uint8Array(buf,4,2);// 以創(chuàng)建 buf 的第 5 位開始取 2 字節(jié)長度創(chuàng)建 TypedArray

//TypedArray 可以直接指定要創(chuàng)建長度,省去手動創(chuàng)建 ArrayBuffer 的過程
var f64a = new Float64Array(8);

//TypedArray 還可以直接輸入數(shù)組
var x = new Uint8Array([1, 1,4,222]);
      x // [1, 1,4,222]

//TypedArray 還可以根據(jù)另一個 TypedArray 克隆
var x = new Uint8Array([1, 1,4,222]);
var y = new Uint8Array(x);
      x // [1, 1,4,222]
      y // [1, 1,4,222]

//TypedArray 還可以與另一個 TypedArray  共用一個 ArrayBuffer
var x = new Uint8Array([1, 1,4,222]);
var y = new Uint8Array(x.buffer); // x.buffer 獲取 x 指向的 ArrayBuffer
      x // [1, 1,4,222]
      y // [1, 1,4,222]
y[0] // 1;
x[0] = 2;
y[0] // 2;

TypedArray 使用數(shù)組的操作方式讀寫指向的 ArrayBuffer ,普通數(shù)組的操作方法和屬性,對TypedArray數(shù)組完全適用。
TypedArray 的 .buffer 屬性表示指向的 ArrayBuffer 。
TypedArray 有 length 屬性,和 byteLength 2個長度屬性,其中 length 表示 TypedArray 數(shù)組成員個數(shù),也就是有多少個,而 byteLength 是這些成員共占多少字節(jié)長度。

Buffer

與上面說的 JavaScript 原生的二進制數(shù)據(jù)操縱方法不同,Buffer 是 Node.js 提供的對象,Buffer 與 TypedArray 類似,都是以數(shù)組的操作方式來處理二進制數(shù)據(jù),只是它不依賴于 ArrayBuffer ,但是 Buffer 也并不是直接存儲數(shù)據(jù),它也是指向一塊數(shù)據(jù),所以多個 Buffer 對象的實例也可以共用一塊數(shù)據(jù)(使用 .slice())。
相較于 TypedArray ,Buffer 在創(chuàng)建時會有更好的性能表現(xiàn),因為 ArrayBuffer 創(chuàng)建時會把申請的內存全白寫 0 ,而 Buffer 創(chuàng)建時沒有這步操作(所以新創(chuàng)建的 Buffer 里會有隨機數(shù)據(jù))。

var buf = new Buffer(16);  //創(chuàng)建 64 個字節(jié)長度的 Buffer

// Buffer 也能從普通數(shù)組創(chuàng)建
var x = new Buffer([1,2,3,4]);

// 由于 TypedArray 也有數(shù)組的特征,所以可以直接把 TypedArray 轉化成 Buffer ,
var v = new Buffer(new Uint8Array([1, 1,4,222]));
        v // {0: 1, 1: 1, 2: 4, 3: 222}

// 反過來 Buffer  也能這樣轉成 TypedArray 
var g = new Uint8Array(v);
        g //[1, 1,4,222]


// Buffer 能根據(jù)另一個 Buffer 克隆 
var y = new Buffer(x);
        y // [1,2,3]
// 所謂克隆自然是深拷貝,與原 Buffer 已經(jīng)毫無關系了
y[0] = 222;
        x[0] // 1
//而使用 .slice() 得到的是指向原 Buffer 指針
z = x.slice(0) //截取 x 從 0 位置到結尾,并賦值給 z ,這截取的只是指針,也就是 x 和 z 共用一段數(shù)據(jù)
          z //{0: 1, 1: 2, 2: 3,} 
z[0] = 222; // z 改變 x 也會同時改變
        x[0] // 222


Buffer 也有 readInt8 、readUInt16LE、writeFloatBE 這樣指定數(shù)據(jù)類型和字節(jié)序的方法,使用起來和 DataView 差不多,不在繁述。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容