Upload images to Imgur from Mathematica

后端 未结 3 949
有刺的猬
有刺的猬 2020-12-13 03:30

Here\'s a challenge to all mathematica tag followers. Let\'s make it a lot more convenient to insert images into SO post from Mathematica by creating an imgur

相关标签:
3条回答
  • 2020-12-13 04:10

    Note: Get an ready-made palette with this functionality here.


    Arnoud's solution got me excited and impatient, so here's an improvement to it. I couldn't have done this without studying his code. This version seems to be somewhat more reliable and less prone to timeout errors, but to be honest, I know no Java at all, so any improvements are welcome.

    Most importantly: this version uploads to stack.imgur.com directly, so it's safe to use here on StackOverflow, without having to worry that uploaded images will disappear after a while.

    I provide three functions:

    • stackImage uploads the expression, exported as PNG, and returns the URL
    • stackMarkdown returns the markdown, ready to be copied
    • stackCopyMarkdown copies the markdown to the clipboard

    Next step: create a palette button that does this automatically for the selected graphic in the notebook. Improvements to the code are very welcome.


    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];
       ]
      ]
    

    Update:

    Here's a button that will show a preview of the selection and will offer uploading (or cancelling). It requires the previous functions to be defined.

    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"]]]]
    

    Unfortunately I can't put the button in a palette (CreatePalette) because the palette dimensions will influence the rasterization. Solutions to this problem are welcome.

    Update 2:

    Based on the answer to this question, here's a working Windows-only palette button:

    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]
    

    Warning: it destroys the clipboard contents even if you click cancel in the preview box.

    0 讨论(0)
  • 2020-12-13 04:13

    A little bird just informed me of a Mathematica solution to this question (the underlying implementation still uses JLink, but this answer hides all the java related code):

    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 <> ")"
    ]
    

    This is V8 only and the XML import options "RequestMethod" and "RequestParameters" are undocumented and experimental (and therefore subject to change).

    0 讨论(0)
  • 2020-12-13 04:30

    Note: This is using the anonymous imgur uploader with my anonymous key. The imgur site restricts uploads to 50 uploads/hour which should be fine normally, but this may cause a problem if a lot of people try this simultaneously. So please get your own anonymous key here:

    http://imgur.com/register/api_anon

    And then replace the key in the code below with your own key (thanks!).

    The trickiest part to code was the conversion from a Mathematica expression to PNG image to Base64 encoding to URL encoding. There are about a 1,000 ways to do it wrong and I think I managed to try them all.

    The code breaks down into a few pieces:

    • Construct the POST url
    • Make the HTTP connection
    • Send the POST url
    • Read back the result, which is XML
    • Extract the imgur url from the XML
    • Format the imgur url as markdown (or as a Mathematica Hyperlink function).

    Here is the code:

    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 <> ")"
      ]
    

    Testing:

    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)
    

    And the actual image:

    Mathematica graphic

    0 讨论(0)
提交回复
热议问题