从Mathematica上传图片到Imgur

53
这里向所有的mathematica标签关注者发出一个挑战。让我们创建一个imgur上传器,使从Mathematica插入图片到SO帖子变得更加方便。
我们如何创建一个函数imgur[g_]来栅格化其参数(确保最终大小不超过StackOverflow帖子的宽度),将其转换为PNG格式,上传到imgur,并返回一个准备好被粘贴的MarkDown行,例如![Mathematica graphic](http://i.imgur.com/ZENa4.jpg)
有用的参考资料: 我未能成功将后一种方法调整为上传图像而无需首先将其导出到文件中。

警告,小心使用! StackOverflow使用 单独的imgur安装 来永久保存图片。如果您使用主要的imgur,如果6个月内没有人查看,则图片将会消失。不幸的是,截至2011年11月,似乎 没有官方方式 可以通过编程方式上传图片到StackOverflow。


更新:请参见下文,有一种直接上传到StackOverflow的解决方案。


2
请注意,上传到 http://i.stack.imgur.com/ 更加困难(您需要“驱动”SO界面)。 - Dr. belisarius
@belisarius 哎呀,我没意识到StackOverflow使用一个单独的imgur网站...在主要的imgur网站上,这些图片可能不会永久保存,所以也许不应该将其用于SO http://imgur.com/faq#long(至少需要1次查看/6个月才能保留)。 - Szabolcs
4
在 V8 中,手动发布图形并不难。右键单击图像,选择“另存为图像”。然后文件对话框会打开到上次的位置,通常是我的桌面,那里已经有一个名为 output.png 的文件等待被其下一个版本覆盖。只需两次点击,再点击两次即可将其发布在我的 SO 回答框中。最多需要 15 秒钟。获取 Markdown 行并粘贴也需要大约同样的时间。 - Sjoerd C. de Vries
@belisarius 但是使用提议的解决方案,您每次想要包含图形时都必须键入 imgur[g],并复制和粘贴Markdown文本。在努力方面似乎真的没有太大的区别。 - Sjoerd C. de Vries
@belisarius 很不幸,目前还没有官方的方法可以上传到 stack.imgur.com。请参见这里:http://stackapps.com/questions/2664/is-there-an-api-to-upload-images-to-ses-imgur-installation/2667#2667 - Szabolcs
显示剩余3条评论
3个回答

16

一只小鸟告诉我有一个 Mathematica 的解决方案适用于这个问题(底层实现仍然使用JLink,但是这个答案隐藏了所有与Java相关的代码):

imgur[expr_] := Module[
 {url, key, image, data, xml, imgurUrl},
 url = "http://api.imgur.com/2/upload";
 key = "c07bc3fb59ef878d5e23a0c4972fbb29";
 image = Fold[ExportString, expr, {"PNG", "Base64"}];
 xml = Import[url, 
  "XML", "RequestMethod" -> "POST", 
  "RequestParameters" -> {"key" -> key, "image" -> image}];
 imgurUrl = Cases[xml, XMLElement["original", {}, {string_}] :> string, 
  Infinity][[1]];
 "![Mathematica graphic](" <> imgurUrl <> ")"
]

这仅适用于V8引擎,XML导入选项"RequestMethod""RequestParameters"是未记录且实验性的(因此可能会更改)。


谢谢分享,Arnoud!不幸的是,当尝试使用我在您其他答案的评论中提到的方法上传到StackOverflow时,似乎这并不起作用。我认为问题在于图像必须以multiplart/form-data提交。你知道怎么做吗?此外,你有什么想法可以将其制作成一个调色板按钮,以上传所选内容(在预览后)?请参见我的其他答案,了解我尝试过什么以及它为什么不起作用(太窄的调色板宽度用于光栅化)。 - Szabolcs
+1:非常好!现在我正在尝试使用OAuth使其能够直接上传图片到我的账户相册等等 :)。另外,你是如何发现那些未记录的选项的? - nixeagle
@nixeagle 如果你成功了,我不介意你在这里发布代码(附带解释挑战和如何解决它们)。 - Szabolcs

13

注意:你可以通过这里获取一个现成的带有此功能的调色板。


Arnoud的解决方案令我兴奋不已,但也让我不耐烦,所以我做了一些改进。没有他的代码学习,我是无法完成这个版本的。这个版本似乎更加可靠,并且更少出现超时错误,但老实说,我对Java一无所知,欢迎提出任何改进意见。

最重要的是:这个版本直接上传到stack.imgur.com,因此在StackOverflow上使用是安全的,而不必担心上传的图片会在一段时间后消失。

我提供了三个功能:

  • stackImage 上传表达式(导出为PNG格式),并返回URL
  • stackMarkdown 返回准备好复制的markdown
  • stackCopyMarkdown 将markdown复制到剪贴板

下一步是创建一个调色板按钮,在笔记本中自动执行此操作以选定图形。非常欢迎对代码进行改进。


Needs["JLink`"]


stackImage::httperr = "Server returned respose code: `1`";
stackImage::err = "Server returner error: `1`";

stackImage[g_] :=
 Module[
  {getVal, url, client, method, data, partSource, part, entity, code, 
   response, error, result},

  (* this function attempts to parse the response fro the SO server *)
  getVal[res_, key_String] :=
   With[{k = "var " <> key <> " = "},
    StringTrim[
     First@StringCases[First@Select[res, StringMatchQ[#, k ~~ ___] &], 
       k ~~ v___ ~~ ";" :> v],
     "'"]
    ];

  data = ExportString[g, "PNG"];

  JavaBlock[
    url = "https://stackoverflow.com/upload/image";
    client = JavaNew["org.apache.commons.httpclient.HttpClient"];
    method = JavaNew["org.apache.commons.httpclient.methods.PostMethod", url];
    partSource = JavaNew["org.apache.commons.httpclient.methods.multipart.ByteArrayPartSource", "mmagraphics.png", MakeJavaObject[data]@toCharArray[]];
    part = JavaNew["org.apache.commons.httpclient.methods.multipart.FilePart", "name", partSource];
    part@setContentType["image/png"];
    entity = JavaNew["org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity", {part}, method@getParams[]];
    method@setRequestEntity[entity];
    code = client@executeMethod[method];
    response = method@getResponseBodyAsString[];
  ]

  If[code =!= 200, Message[stackImage::httperr, code]; Return[$Failed]];
  response = StringTrim /@ StringSplit[response, "\n"];

  error = getVal[response, "error"];
  result = getVal[response, "result"];
  If[StringMatchQ[result, "http*"],
   result,
   Message[stackImage::err, error]; $Failed]
  ]


stackMarkdown[g_] := "![Mathematica graphics](" <> stackImage[g] <> ")"


stackCopyMarkdown[g_] := Module[{nb, markdown},
  markdown = Check[stackMarkdown[g], $Failed];
  If[markdown =!= $Failed,
   nb = NotebookCreate[Visible -> False];
   NotebookWrite[nb, Cell[markdown, "Text"]];
   SelectionMove[nb, All, Notebook];
   FrontEndTokenExecute[nb, "Copy"];
   NotebookClose[nb];
   ]
  ]

更新:

这里有一个按钮,可以显示所选内容的预览,并提供上传(或取消)选项。但需要先定义之前的函数。

Button["Upload to SO",
 Module[{cell = NotebookRead@InputNotebook[], img},
  If[cell =!= {}, img = Rasterize[cell];
   MessageDialog[
    Column[{"Upload image to StackExchange sites?", 
      img}], {"Upload and copy MarkDown" :> stackCopyMarkdown[img], 
     "Cancel" :> Null}, WindowTitle -> "Upload to StackExchange"]]]]

很遗憾,我无法将按钮放在调色板(CreatePalette)中,因为调色板的尺寸会影响光栅化。欢迎提出解决此问题的方案。

更新2:

根据这个问题的答案,在Windows系统下可使用下面的代码实现调色板按钮:

button = Button["Upload to SO",
  Module[{sel},
   FrontEndExecute[
    FrontEndToken[FrontEnd`SelectedNotebook[], "CopySpecial", "MGF"]];
   sel = Cases[NotebookGet@ClipboardNotebook[], 
     RasterBox[data_, ___] :> 
      Image[data, "Byte", ColorSpace -> "RGB", Magnification -> 1], 
     Infinity];
   If[sel =!= {},
    With[{img = First[sel]},
     MessageDialog[
      Column[{"Upload image to StackExchange sites?", 
        img}], {"Upload and copy MarkDown" :> stackCopyMarkdown[img], 
       "Cancel" :> Null}, WindowTitle -> "Upload to StackExchange"]
     ]
    ]
   ]
  ]

CreatePalette[button]

警告:即使您在预览框中点击取消,它也会破坏剪贴板内容。


我认为栅格化调色板问题值得另外开一个问题进行讨论。 - Mr.Wizard
太好了!我一直在为此而保留我的投票。***+1*** - Mr.Wizard
1
@Mr.Wizard请查看聊天室以获取打包好的解决方案。 - Szabolcs

12

注意:这里使用的是匿名的Imgur上传器和我的匿名密钥。Imgur网站每小时限制上传50次,通常情况下应该没问题,但如果有很多人同时尝试,则可能会出现问题。所以,请在此处获取您自己的匿名密钥:

http://imgur.com/register/api_anon

然后用您自己的密钥替换下面代码中的密钥(谢谢!)。

编码最困难的部分是从Mathematica表达式转换为PNG图像,再到Base64编码,最后进行URL编码。有大约1000种方法可以做错它,我想我已经试过了所有方法。

该代码分成几个部分:

  • 构造POST url
  • 建立HTTP连接
  • 发送POST url
  • 读取结果,即XML
  • 从XML提取imgur url
  • 将imgur url格式化为markdown(或Mathematica Hyperlink函数)。

以下是代码:

imgur[expr_] :=
 Module[{url, key, image, data, jUrl, jConn, jWriter, jInput, buffer,
   byte, xml, imgurUrl},
  Needs["JLink`"];
  JLink`JavaBlock[
   JLink`LoadJavaClass["java.net.URLEncoder"];
   url = "http://api.imgur.com/2/upload";
   key = "c07bc3fb59ef878d5e23a0c4972fbb29";
   image = ExportString[ExportString[expr, "PNG"], "Base64"];
   data =
    URLEncoder`encode["key"   , "UTF-8"] <> "=" <>
    URLEncoder`encode[ key    , "UTF-8"] <> "&" <>
    URLEncoder`encode["image" , "UTF-8"] <> "=" <>
    URLEncoder`encode[ image  , "UTF-8"] ;
   jUrl = JLink`JavaNew["java.net.URL", url];
   jConn = jUrl@openConnection[];
   jConn@setDoOutput[True];
   jWriter =
    JLink`JavaNew["java.io.OutputStreamWriter",
     jConn@getOutputStream[]];
   jWriter@write[data];
   jWriter@flush[];
   jInput = jConn@getInputStream[];
   buffer = {};
   While[(byte = jInput@read[]; byte >= 0), AppendTo[buffer, byte]];
   ];
  xml = ImportString[FromCharacterCode[buffer], "XML"];
  imgurUrl =
   Cases[xml,
     XMLElement["original", {}, {string_}] :>
      string, \[Infinity]][[1]];
  "![Mathematica graphic](" <> imgurUrl <> ")"
  ]

测试:

In[]:= g = Graphics[{Blue, Disk[]}, PlotRange -> 1.2, ImageSize -> Small];
       pic = Overlay[{Blur[Binarize@g, 10], g}];
       imgur[pic]

Out[]= ![Mathematica graphic](http://i.imgur.com/eGOlL.png)

以下是实际图片:

Mathematica graphic


有没有办法在Mathematica本身中进行编码和上传,而不必诉诸于JLink?顺便加一分。 - rcollyer
1
我收到了错误信息:``Java::excptn: 发生了Java异常:java.io.IOException: 服务器返回HTTP响应代码:400,URL为:http://api.imgur.com/2/upload 在sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1290)。XMLParserXMLGetString::string: 在XMLParserXMLGetString[EndOfFile]的位置1处期望字符串。`` - Mr.Wizard
2
@Mr.Wizard -- 我现在也在V7中看到了这个问题。 V7在Base64编码方面做了一些不同的事情:ExportString [ExportString [1,“PNG”],“Base64”]。我正在寻找V7的解决方案。 - Arnoud Buzing
1
此外,像那样组合查询字符串似乎有点笨拙。我可能会用queryString[enc_String, q:{{_String, _String}..}] := StringJoin@Riffle[ URLEncoder`encode[#1,enc]<> "="<> URLEncoder`encode[#2,enc]& @@@ q,"&"]来替换它。 - rcollyer
2
@Mr.Wizard -- 在V7中,将设置图像的行替换为以下内容:image = Developer`EncodeBase64[ExportString[expr, "PNG"]]; 如果这解决了V7问题,请告诉我(对我来说确实有效)。 - Arnoud Buzing
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接