1. 程式人生 > >滿紙荒唐言:Processing 尋覓文字魅力篇

滿紙荒唐言:Processing 尋覓文字魅力篇

本文要說的:

滿紙荒唐言

亂哄哄,你方唱罷我登場,反認他鄉是故鄉;甚荒唐,到頭來都是為他人作嫁衣裳。

“滿紙荒唐言”出自小說《紅樓夢》的開篇。正所謂,“滿紙荒唐言,一把辛酸淚。都雲作者痴,誰解其中味?”雪芹先生於悼紅軒中,披閱十載,增刪五次,纂成目錄,分出章回,又題曰《金陵十二釵》,並題了該詩。

吾近來讀之,甚喜。自以為文章合本意,故以此為引。

古人有云,“聲不能傳於異地,留於異時,於是乎文字生。文字者,所以為意與聲之跡。”

當然,本文志在體味“脫帽露頂王公前,揮毫落紙如雲煙”的灑脫與超然,而非細究文字的“傳於異地,留於異時”的實用性。

就藝術的眼光看來,文字即無言之詩,無形之舞,無圖之畫,無聲之樂。

這不禁讓我想起了2008年北京奧運會開幕式的場景,國人向全世界展示中華文字藝術的魅力,好不壯觀。

以下,是我用 Processing 製作的一個標題。程式碼線上,點選這裡在這裡插入圖片描述

文字學習篇

於程式而言,其顯示步驟如下:

step 1:Create the font;

至於建立字型,吾有三計——

計之一:利用建立字型工具手動建立字型。

在 Processing 工作視窗的“工具”(Tools)選單中選擇“建立字型…”(Create Font)命令,在彈出的視窗中選擇字型和字號,點選“確定”(OK)完成建立。生成的 vlw 格式字型被預設放置於 data 資料夾中(若沒有,PDE 會自動建立 data 資料夾)中。(“vlw”是 Processing 特有的字型格式)

計之二:使用使用者系統中的字型。

如何檢視使用者系統中有字型呢?呼叫 PFont.list() 函式:

String[] list=PFont.list();
printArray(list);  // 在控制檯輸出字型名稱

換而言之,我們可以在自己的電腦系統裡安裝好所需的字型,而後在 Processing 直接載入呼叫。

而美中不足的是,這種方式會導致程式的載入速度變緩,畢竟我們增大了載入難度。同時這也並非所有字型都可以使用,有些可能只適用於一個作業系統。

計之三:使用網上下載的字型。

此計可謂是結合了前二計的綜合法。一為將下載好字型放置於 data 資料夾中(計一),一為在使用者系統上安裝好下載的字型(計二)。

step 2:Declare an object of type PFont;

宣告變數:

// PFont + 變數名
   PFont font;	// 變數名為 font

step 3:Load the font;

這裡理應有兩種做法:

法之一:當需要將.vlw格式的字型載入到PFont物件時,我們呼叫 loadFont() 方法。

舉個栗子:

PFont font;
// 字型必須位於“data”資料夾中才能成功載入
font = loadFont("LetterGothicStd-32.vlw");

法之二:利用 createFont() 方法,將“data”資料夾中的.ttf.otf檔案或計算機上其他位置安裝好的字型,動態地轉換為 Processing 使用的格式。

舉個栗子:

PFont myFont;

void setup() {
  size(200, 200);
  // 取消以下兩行註釋,以檢視可用字型
  // String[] fontList = PFont.list();
  // printArray(fontList);
  myFont = createFont("Georgia", 32);
}

step 4:Specify the font;

textFont():設定將使用text()繪製的當前字型。其可輸入一個或兩個引數,字型變數和字型大小。若輸入不包含字型大小,則字型將顯示為字型的原始載入大小。值得注意的是,如果我們指定的字型大小與載入的字型大小不同,則可能會顯示畫素化或質量差的文字。

舉個栗子:

textFont(f,36);

step 5:Specify a color;

上色工序,你可能需要:

fill(rgb)
fill(rgb, alpha)
fill(gray)
fill(gray, alpha)
fill(v1, v2, v3)
fill(v1, v2, v3, alpha)

step 6:Display text。

顯示文字咯!詳細請查閱text()

舉個栗子:

text("word", 10, 90);

進階成長錄

厚地高天,堪嘆古今情不盡;痴男怨女,可憐風月債難償。

欲取文臺幻境,且需步步為營。

在下文中,愚笨的筆者舉了一個小小的栗子,以期讀者能夠領悟如何由淺入深、循序漸進地增長自身功力,成為絕世大俠。

小栗子的結構:

栗子形: 文字跳動栗子殼: 天外飛仙栗子肉: 落絮來書栗子味: 還是得自己嘗!
栗子皮:文字跳動

【明】袁巨集道言:一林過雨蘆花白,半壁疏雲栗子黃。

程式碼線上,點選這裡在這裡插入圖片描述

栗子殼: 天外飛仙

無名氏言:毛刺隨身霧裡狂,針球栗子鬱山鄉。

程式碼線上,點選這裡在這裡插入圖片描述

栗子肉: 落絮來書

京津傳言:堆盤栗子炒深黃,客到長談索酒嘗。寒火三更燈半灺,門前高喊“灌香糖”。

程式碼線上,點選這裡在這裡插入圖片描述

栗子味: 還是得自己嘗!

【元】王哲曰:栗子甘甜美芋頭。

……

字型設計稿

關於創意字型設計,這裡有兩個可謂實用的庫(NextText 已在 Processing 3 中棄用了,此處且不再提及):

Ⅰ:Geomerative,By Ricard Marxer

Introduction:

Geomerative 擴充套件了 2D 幾何操作,以促進生成幾何。其包括了 TrueType 字型和 SVG 直譯器。

Ⅱ:Fontastic,By Andreas Koller

Introduction:

Fontastic 用於建立 TTF 和 WOFF 格式的字型檔案。

所以,你該用誰呢? 以筆者的理解,前者如雕刻刀,後者若鑿石斧。Geomerative 追求創意,Fontastic 旨在簡易。

當然,我也收集到了一些使用 Geomerative 建立的字型:

在這裡插入圖片描述 在這裡插入圖片描述 在這裡插入圖片描述 在這裡插入圖片描述 在這裡插入圖片描述 在這裡插入圖片描述 在這裡插入圖片描述 在這裡插入圖片描述 在這裡插入圖片描述

金陵十二釵

可嘆的是,這裡並不談論《石頭記》裡面那些薄命女子,而是以87版《紅樓夢》十二釵的美豔劇照為本,編寫出一個以文字繪製肖像的創意小程式——“Twelve Beauties of Jinling”。

該程式我已在 OpenProcessing 上傳,獲取之,然而其執行效果尚不理想,但倘若讀者利用 Processing 執行,它確實沒有問題。這可能是網站的 Bug,目前我也沒找到較好的解決辦法。

因此,我在 CSDN 上補發了一份完整的原始碼+資源,點選這裡

以下,為其完整的原始碼:

/**
 * Twelve_Beauties_of_Jinling
 *
 * By Hewes 18/11/6
 * https://blog.csdn.net/Hewes
 * Controls:
 * MousePress - 切換美人圖
 *
 **/

//String[] imgNames = {"黛玉.jpg", "寶釵.jpg", "元春.jpg", 
//  "探春.jpg", "湘雲.jpg", "妙玉.jpg", "熙鳳.jpg", "惜春.jpg", 
//  "迎春.jpg", "巧姐.jpg", "李紈.jpg", "可卿.jpg"};
String[] imgNames = {"黛玉:欠淚的淚已盡.jpg", "寶釵:富貴的金銀散盡.jpg", 
  "元春:欲知命短問前生.jpg", "探春:分離聚合皆前定.jpg", "湘雲:為官的家業凋零.jpg", 
  "妙玉:無情的分明報應.jpg", "熙鳳:痴迷的枉送了性命.jpg", "惜春:看破的遁入空門.jpg", 
  "迎春:欠命的命已還.jpg", "巧姐:有恩的死裡逃生.jpg", "李紈:老來富貴也真僥倖.jpg", 
  "可卿:冤冤相報豈非輕.jpg"};
PImage img, bg;
int imgIndex = 0, index=0;
String[] lines;
PFont font1, font2;
char[] chars;
String displayText;
float cx, cy, r, degree;
float minWeight = 2, maxWeight = 6, currWeight;
float spacing = maxWeight;  // 字元間隔
float goldenRatio = (sqrt(5) + 1 ) / 2;
int iter = 0;

void setup() {
  size(900, 900);
  frameRate(30);
  font1 = createFont("楷體", 10);
  font2 = createFont("隸書", 80);
  bg=loadImage("bg.jpg");
  bg.resize(width, height);
  textAlign(CENTER, CENTER);
  lines=loadStrings("十二釵判詞.txt");  // 讀取並建立其各行的 String 陣列
  nextImage();  // 下一張肖像
}

void draw() {
  pushMatrix();
  translate(width/2-120, height/2);
  if (cx>= 0 && 1.1*cx<= img.width && cy >= 0 && 1.1*cy<= img.height ) { 
    for (int i = 30; i > 0; --i) {
      index = index % displayText.length();  // 遍歷文字
      degree = (iter * goldenRatio) * 360;  // 字元的角度
      r = sqrt(iter++) * spacing;
      // 計算字元的位置
      calcCharPos(img.width/2, img.height/2, r, (degree % 360));
      color pix = img.get((int)cx, (int)cy);
      currWeight = map(brightness(pix), 255, 0, minWeight, maxWeight);
      textSize(currWeight*2);
      fill(pix);
      text(chars[index], 1.3*(cx-img.width/2), 1.3*(cy-img.height/2));
      index++;
    }
  }
  popMatrix();
}

// 計算文字的位置
void calcCharPos(float x, float y, float radius, float angle) {
  cx = x + cos(radians(angle))*(radius/2);
  cy = y + sin(radians(angle))*(radius/2);
}

// 下一張肖像
void nextImage() {
  background(bg);
  textFont(font1);
  img = loadImage(imgNames[imgIndex]);
  img.resize(520, 520);
  img.loadPixels();  // 將當前顯示視窗的快照載入到 pixels[] 陣列中
  displayText=loadText(lines[imgIndex]);  // 文字更新載入
  // 顯示美人名
  textFont(font2);
  fill(0, 80);
  text(chars[0], 750, 350);
  text(chars[1], 750, 550);
  textFont(font1);
  // 迴圈圖片索引
  imgIndex += 1;
  if (imgIndex >=imgNames.length) {
    imgIndex = 0;
  }
  index = 0;
  iter = 0;
  cx = 0;
  cy = 0;
}

// 載入字元,需輸入其索引
String loadText(String inText) {
  String allText=join(lines, " ");
  chars=new char[inText.length()];
  int i=0;
  int charPos = 0;
  if (imgIndex==0) {
    charPos=1;
  }
  char currChar;  // 當前字元
  // 遍歷片段內字元,儲存到 displayText 中
  while (charPos < inText.length()) {
    currChar = inText.charAt(charPos);
    charPos++;
    chars[i]=currChar;
    i++;
  }
  //println(new String(chars));
  return new String(chars);  // 返回將顯示的判詞
}

void mousePressed() {
  nextImage();  // 下一張肖像
}