[{"data":1,"prerenderedAt":2065},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Finsert-an-image-from-memory-into-excel":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Finsert-an-image-from-memory-into-excel":2031,"ancestors:\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Finsert-an-image-from-memory-into-excel":2040,"children:\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Finsert-an-image-from-memory-into-excel":2045,"related:\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Finsert-an-image-from-memory-into-excel":2046},{"id":4,"title":5,"body":6,"dateModified":1998,"datePublished":1998,"description":1999,"extension":2000,"faq":2001,"keyword":2010,"kind":2011,"meta":2012,"navigation":270,"path":2021,"seo":2022,"slug":2025,"stem":2026,"topics":2027,"type":2029,"__hash__":2030},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Finsert-an-image-from-memory-into-excel\u002Findex.md","Insert an Image from Memory into Excel with Python",{"type":7,"value":8,"toc":1984},"minimark",[9,24,192,197,229,232,509,513,520,603,618,622,634,720,730,899,903,909,1006,1009,1138,1145,1149,1152,1360,1370,1498,1502,1513,1613,1632,1636,1645,1787,1790,1794,1804,1862,1869,1873,1905,1909,1921,1925,1931,1937,1943,1949,1953,1980],[10,11,12,13,18,19,23],"p",{},"Report images rarely start as files on disk. A company logo comes from a URL, a product photo from a database column, a signature or chart from an API as a base64 string, a thumbnail from Pillow after resizing. Writing each one to a temporary file just to read it back into Excel is slow, leaves files behind and breaks in read-only containers. Both openpyxl and XlsxWriter accept image bytes directly. This guide, part of ",[14,15,17],"a",{"href":16},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002F","Inserting Images and Logos into Excel",", covers the common sources, the resizing and format conversions worth doing in memory, and the error that only shows up when the workbook is saved. For charts rendered with matplotlib, see ",[14,20,22],{"href":21},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fembed-a-matplotlib-chart-in-an-excel-report\u002F","Embed a Matplotlib Chart in an Excel Report",".",[25,26,34,35,34,39,34,43,34,51,34,61,34,72,34,79,34,84,34,92,34,99,34,105,34,110,34,113,34,118,34,123,34,130,34,135,34,139,34,142,34,144,34,147,34,153,34,157,34,161,34,164,34,166,34,171,34,178,34,183,34,187,34,190],"svg",{"viewBox":27,"role":28,"ariaLabelledBy":29,"xmlns":32,"style":33},"4 0 752 436","img",[30,31],"im-sources-t","im-sources-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[36,37,38],"title",{"id":30},"Image bytes from anywhere, straight into the workbook",[40,41,42],"desc",{"id":31},"Bytes downloaded over HTTP, base64 text from an API response, a BLOB column from a database and an image resized with Pillow are each wrapped in a BytesIO and inserted into the workbook without any temporary file.",[44,45],"rect",{"x":46,"y":47,"width":48,"height":49,"fill":50},"4","0","752","436","#ffffff",[44,52],{"x":53,"y":54,"width":55,"height":56,"rx":57,"fill":58,"stroke":59,"style":60},"520.0","171.5","220.0","93.0","12","#d9f4f1","var(--teal,#0f9488)","stroke-width:2px",[62,63,65],"g",{"transform":64},"translate(618.0,184.5) scale(1.000)",[66,67],"path",{"d":68,"fill":69,"stroke":70,"style":71},"M6 3h9l4 4v14H6z M15 3v4h4 M9.5 11.5l4.5 6 M14 11.5l-4.5 6","none","var(--teal-ink,#0b6157)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[73,74,78],"text",{"x":75,"y":76,"style":77},"630.0","231.0","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Workbook",[73,80,83],{"x":75,"y":81,"style":82},"250.5","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Image(BytesIO(data))",[44,85],{"x":86,"y":87,"width":88,"height":89,"rx":57,"fill":90,"stroke":91,"style":60},"20.0","16.0","250.0","92.0","#f0f4ff","var(--brand,#5b5cf0)",[62,93,95],{"transform":94},"translate(133.0,29.0) scale(1.000)",[66,96],{"d":97,"fill":69,"stroke":98,"style":71},"M12 3a9 9 0 1 0 .01 0z M3 12h18 M12 3c3.2 3 3.2 15 0 18 M12 3c-3.2 3-3.2 15 0 18","var(--brand-strong,#4338ca)",[73,100,104],{"x":101,"y":102,"style":103},"145.0","75.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","HTTP download",[73,106,109],{"x":101,"y":107,"style":108},"94.0","font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","response.content",[66,111],{"d":112,"fill":69,"stroke":91,"style":60},"M274.0,62.0 C394.0,62.0 394.0,218.0 510.0,218.0",[114,115],"polygon",{"points":116,"fill":117},"514.0,218.0 505.0,223.0 505.0,213.0","#5b5cf0",[44,119],{"x":86,"y":120,"width":88,"height":89,"rx":57,"fill":121,"stroke":122,"style":60},"120.0","#fdefd8","var(--gold,#b4740a)",[62,124,126],{"transform":125},"translate(133.0,133.0) scale(1.000)",[66,127],{"d":128,"fill":69,"stroke":129,"style":71},"M8 7l-5 5 5 5 M16 7l5 5-5 5 M14 4l-4 16","var(--gold-ink,#7a4e06)",[73,131,134],{"x":101,"y":132,"style":133},"179.0","font-size:13px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","API field",[73,136,138],{"x":101,"y":137,"style":108},"198.0","base64 text",[66,140],{"d":141,"fill":69,"stroke":91,"style":60},"M274.0,166.0 C394.0,166.0 394.0,218.0 510.0,218.0",[114,143],{"points":116,"fill":117},[44,145],{"x":86,"y":146,"width":88,"height":89,"rx":57,"fill":90,"stroke":91,"style":60},"224.0",[62,148,150],{"transform":149},"translate(133.0,237.0) scale(1.000)",[66,151],{"d":152,"fill":69,"stroke":98,"style":71},"M5 6c0-1.7 3.1-3 7-3s7 1.3 7 3-3.1 3-7 3-7-1.3-7-3z M5 6v12c0 1.7 3.1 3 7 3s7-1.3 7-3V6 M5 12c0 1.7 3.1 3 7 3s7-1.3 7-3",[73,154,156],{"x":101,"y":155,"style":103},"283.0","Database BLOB",[73,158,160],{"x":101,"y":159,"style":108},"302.0","bytes column",[66,162],{"d":163,"fill":69,"stroke":91,"style":60},"M274.0,270.0 C394.0,270.0 394.0,218.0 510.0,218.0",[114,165],{"points":116,"fill":117},[44,167],{"x":86,"y":168,"width":88,"height":89,"rx":57,"fill":169,"stroke":170,"style":60},"328.0","#fee8f2","var(--accent,#f43f8f)",[62,172,174],{"transform":173},"translate(133.0,341.0) scale(1.000)",[66,175],{"d":176,"fill":69,"stroke":177,"style":71},"M4 5h16v14H4z M4 16l5-5 4 4 3-3 4 4 M15 9h.01","var(--accent-ink,#be185d)",[73,179,182],{"x":101,"y":180,"style":181},"387.0","font-size:13px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","Pillow",[73,184,186],{"x":101,"y":185,"style":108},"406.0","resized in memory",[66,188],{"d":189,"fill":69,"stroke":91,"style":60},"M274.0,374.0 C394.0,374.0 394.0,218.0 510.0,218.0",[114,191],{"points":116,"fill":117},[193,194,196],"h2",{"id":195},"prerequisites","Prerequisites",[198,199,204],"pre",{"className":200,"code":201,"language":202,"meta":203,"style":203},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl pillow xlsxwriter\n","bash","",[205,206,207],"code",{"__ignoreMap":203},[208,209,212,216,220,223,226],"span",{"class":210,"line":211},"line",1,[208,213,215],{"class":214},"sMTad","pip",[208,217,219],{"class":218},"srMev"," install",[208,221,222],{"class":218}," openpyxl",[208,224,225],{"class":218}," pillow",[208,227,228],{"class":218}," xlsxwriter\n",[10,230,231],{},"openpyxl needs Pillow to read image dimensions and formats. The examples create a small PNG in memory to stand in for a real logo:",[198,233,237],{"className":234,"code":235,"language":236,"meta":203,"style":203},"language-python shiki shiki-themes github-light github-dark-high-contrast","import base64\nimport io\nimport sqlite3\n\nfrom openpyxl import Workbook, load_workbook\nfrom openpyxl.drawing.image import Image\nfrom PIL import Image as PILImage, ImageDraw\n\ndef make_logo(text=\"ACME\", size=(480, 160), colour=(67, 56, 202)):\n    picture = PILImage.new(\"RGB\", size, colour)\n    ImageDraw.Draw(picture).text((24, 64), text, fill=\"white\")\n    buffer = io.BytesIO()\n    picture.save(buffer, format=\"PNG\")\n    return buffer.getvalue()\n\nlogo_bytes = make_logo()\nprint(len(logo_bytes) > 0)          # True\n","python",[205,238,239,249,257,265,272,286,299,320,325,384,401,430,441,457,466,471,482],{"__ignoreMap":203},[208,240,241,245],{"class":210,"line":211},[208,242,244],{"class":243},"s-kum","import",[208,246,248],{"class":247},"skGVy"," base64\n",[208,250,252,254],{"class":210,"line":251},2,[208,253,244],{"class":243},[208,255,256],{"class":247}," io\n",[208,258,260,262],{"class":210,"line":259},3,[208,261,244],{"class":243},[208,263,264],{"class":247}," sqlite3\n",[208,266,268],{"class":210,"line":267},4,[208,269,271],{"emptyLinePlaceholder":270},true,"\n",[208,273,275,278,281,283],{"class":210,"line":274},5,[208,276,277],{"class":243},"from",[208,279,280],{"class":247}," openpyxl ",[208,282,244],{"class":243},[208,284,285],{"class":247}," Workbook, load_workbook\n",[208,287,289,291,294,296],{"class":210,"line":288},6,[208,290,277],{"class":243},[208,292,293],{"class":247}," openpyxl.drawing.image ",[208,295,244],{"class":243},[208,297,298],{"class":247}," Image\n",[208,300,302,304,308,311,314,317],{"class":210,"line":301},7,[208,303,277],{"class":243},[208,305,307],{"class":306},"sP0c6"," PIL",[208,309,310],{"class":243}," import",[208,312,313],{"class":247}," Image ",[208,315,316],{"class":243},"as",[208,318,319],{"class":247}," PILImage, ImageDraw\n",[208,321,323],{"class":210,"line":322},8,[208,324,271],{"emptyLinePlaceholder":270},[208,326,328,331,335,338,341,344,347,349,352,355,358,361,364,366,368,371,373,376,378,381],{"class":210,"line":327},9,[208,329,330],{"class":243},"def",[208,332,334],{"class":333},"s_Opv"," make_logo",[208,336,337],{"class":247},"(text",[208,339,340],{"class":243},"=",[208,342,343],{"class":218},"\"ACME\"",[208,345,346],{"class":247},", size",[208,348,340],{"class":243},[208,350,351],{"class":247},"(",[208,353,354],{"class":306},"480",[208,356,357],{"class":247},", ",[208,359,360],{"class":306},"160",[208,362,363],{"class":247},"), colour",[208,365,340],{"class":243},[208,367,351],{"class":247},[208,369,370],{"class":306},"67",[208,372,357],{"class":247},[208,374,375],{"class":306},"56",[208,377,357],{"class":247},[208,379,380],{"class":306},"202",[208,382,383],{"class":247},")):\n",[208,385,387,390,392,395,398],{"class":210,"line":386},10,[208,388,389],{"class":247},"    picture ",[208,391,340],{"class":243},[208,393,394],{"class":247}," PILImage.new(",[208,396,397],{"class":218},"\"RGB\"",[208,399,400],{"class":247},", size, colour)\n",[208,402,404,407,410,412,415,418,422,424,427],{"class":210,"line":403},11,[208,405,406],{"class":247},"    ImageDraw.Draw(picture).text((",[208,408,409],{"class":306},"24",[208,411,357],{"class":247},[208,413,414],{"class":306},"64",[208,416,417],{"class":247},"), text, ",[208,419,421],{"class":420},"sa561","fill",[208,423,340],{"class":243},[208,425,426],{"class":218},"\"white\"",[208,428,429],{"class":247},")\n",[208,431,433,436,438],{"class":210,"line":432},12,[208,434,435],{"class":247},"    buffer ",[208,437,340],{"class":243},[208,439,440],{"class":247}," io.BytesIO()\n",[208,442,444,447,450,452,455],{"class":210,"line":443},13,[208,445,446],{"class":247},"    picture.save(buffer, ",[208,448,449],{"class":420},"format",[208,451,340],{"class":243},[208,453,454],{"class":218},"\"PNG\"",[208,456,429],{"class":247},[208,458,460,463],{"class":210,"line":459},14,[208,461,462],{"class":243},"    return",[208,464,465],{"class":247}," buffer.getvalue()\n",[208,467,469],{"class":210,"line":468},15,[208,470,271],{"emptyLinePlaceholder":270},[208,472,474,477,479],{"class":210,"line":473},16,[208,475,476],{"class":247},"logo_bytes ",[208,478,340],{"class":243},[208,480,481],{"class":247}," make_logo()\n",[208,483,485,488,490,493,496,499,502,505],{"class":210,"line":484},17,[208,486,487],{"class":306},"print",[208,489,351],{"class":247},[208,491,492],{"class":306},"len",[208,494,495],{"class":247},"(logo_bytes) ",[208,497,498],{"class":243},">",[208,500,501],{"class":306}," 0",[208,503,504],{"class":247},")          ",[208,506,508],{"class":507},"s-wDw","# True\n",[193,510,512],{"id":511},"step-1-bytes-into-openpyxl","Step 1 — Bytes into openpyxl",[10,514,515,516,519],{},"Wrap the bytes in ",[205,517,518],{},"io.BytesIO"," and pass the buffer where a filename would go:",[198,521,523],{"className":234,"code":522,"language":236,"meta":203,"style":203},"wb = Workbook()\nws = wb.active\nlogo = Image(io.BytesIO(logo_bytes))\nprint(logo.format, logo.width, logo.height)       # png 480 160\nlogo.width, logo.height = 240, 80                  # display at half size\nws.add_image(logo, \"A1\")\nwb.save(\"with_logo.xlsx\")\n",[205,524,525,535,545,555,565,583,593],{"__ignoreMap":203},[208,526,527,530,532],{"class":210,"line":211},[208,528,529],{"class":247},"wb ",[208,531,340],{"class":243},[208,533,534],{"class":247}," Workbook()\n",[208,536,537,540,542],{"class":210,"line":251},[208,538,539],{"class":247},"ws ",[208,541,340],{"class":243},[208,543,544],{"class":247}," wb.active\n",[208,546,547,550,552],{"class":210,"line":259},[208,548,549],{"class":247},"logo ",[208,551,340],{"class":243},[208,553,554],{"class":247}," Image(io.BytesIO(logo_bytes))\n",[208,556,557,559,562],{"class":210,"line":267},[208,558,487],{"class":306},[208,560,561],{"class":247},"(logo.format, logo.width, logo.height)       ",[208,563,564],{"class":507},"# png 480 160\n",[208,566,567,570,572,575,577,580],{"class":210,"line":274},[208,568,569],{"class":247},"logo.width, logo.height ",[208,571,340],{"class":243},[208,573,574],{"class":306}," 240",[208,576,357],{"class":247},[208,578,579],{"class":306},"80",[208,581,582],{"class":507},"                  # display at half size\n",[208,584,585,588,591],{"class":210,"line":288},[208,586,587],{"class":247},"ws.add_image(logo, ",[208,589,590],{"class":218},"\"A1\"",[208,592,429],{"class":247},[208,594,595,598,601],{"class":210,"line":301},[208,596,597],{"class":247},"wb.save(",[208,599,600],{"class":218},"\"with_logo.xlsx\"",[208,602,429],{"class":247},[10,604,605,606,609,610,613,614,23],{},"openpyxl reads the format and pixel size through Pillow. Setting ",[205,607,608],{},"width"," and ",[205,611,612],{},"height"," changes how large the picture is displayed, in pixels, without touching the stored bytes. Positioning and anchoring are covered in ",[14,615,617],{"href":616},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fresize-and-position-images-in-excel-with-openpyxl\u002F","Resize and Position Images in Excel with openpyxl",[193,619,621],{"id":620},"step-2-keep-the-buffer-open-until-the-save","Step 2 — Keep the buffer open until the save",[10,623,624,625,629,630,633],{},"The one surprise with in-memory images: openpyxl reads the image data when the workbook is ",[626,627,628],"strong",{},"saved",", not when the ",[205,631,632],{},"Image"," is created. A buffer closed in between makes the save fail, far from the line that caused it:",[198,635,637],{"className":234,"code":636,"language":236,"meta":203,"style":203},"wb = Workbook()\nws = wb.active\nwith io.BytesIO(logo_bytes) as buffer:\n    ws.add_image(Image(buffer), \"A1\")\ntry:\n    wb.save(\"broken.xlsx\")\nexcept ValueError as exc:\n    print(exc)                                     # I\u002FO operation on closed file.\n",[205,638,639,647,655,668,677,685,695,709],{"__ignoreMap":203},[208,640,641,643,645],{"class":210,"line":211},[208,642,529],{"class":247},[208,644,340],{"class":243},[208,646,534],{"class":247},[208,648,649,651,653],{"class":210,"line":251},[208,650,539],{"class":247},[208,652,340],{"class":243},[208,654,544],{"class":247},[208,656,657,660,663,665],{"class":210,"line":259},[208,658,659],{"class":243},"with",[208,661,662],{"class":247}," io.BytesIO(logo_bytes) ",[208,664,316],{"class":243},[208,666,667],{"class":247}," buffer:\n",[208,669,670,673,675],{"class":210,"line":267},[208,671,672],{"class":247},"    ws.add_image(Image(buffer), ",[208,674,590],{"class":218},[208,676,429],{"class":247},[208,678,679,682],{"class":210,"line":274},[208,680,681],{"class":243},"try",[208,683,684],{"class":247},":\n",[208,686,687,690,693],{"class":210,"line":288},[208,688,689],{"class":247},"    wb.save(",[208,691,692],{"class":218},"\"broken.xlsx\"",[208,694,429],{"class":247},[208,696,697,700,703,706],{"class":210,"line":301},[208,698,699],{"class":243},"except",[208,701,702],{"class":306}," ValueError",[208,704,705],{"class":243}," as",[208,707,708],{"class":247}," exc:\n",[208,710,711,714,717],{"class":210,"line":322},[208,712,713],{"class":306},"    print",[208,715,716],{"class":247},"(exc)                                     ",[208,718,719],{"class":507},"# I\u002FO operation on closed file.\n",[10,721,722,723,726,727,729],{},"Create the ",[205,724,725],{},"BytesIO"," without a ",[205,728,659],{}," block, or keep the block open around the save. A buffer made from bytes holds no file handle, so leaving it open costs nothing; it is freed with the workbook.",[25,731,34,736,34,739,34,742,34,745,34,749,34,755,34,760,34,765,34,772,34,776,34,779,34,785,34,789,34,793,34,795,34,797,34,802,34,805,34,813,34,819,34,823,34,826,34,831,34,835,34,840,34,843,34,848,34,853,34,856,34,859,34,864,34,869,34,872,34,875,34,880,34,884,34,887,34,890,34,895],{"viewBox":732,"role":28,"ariaLabelledBy":733,"xmlns":32,"style":33},"4 0 752 418",[734,735],"im-timeline-t","im-timeline-d",[36,737,738],{"id":734},"When openpyxl actually reads the image bytes",[40,740,741],{"id":735},"The script creates a BytesIO, builds an openpyxl Image from it and adds it to the sheet; openpyxl only reads the image size at this point. The pixel data is read later, during wb.save. If the with block has already closed the buffer, the save raises ValueError I\u002FO operation on closed file.",[44,743],{"x":46,"y":47,"width":48,"height":744,"fill":50},"418",[44,746],{"x":86,"y":87,"width":747,"height":748,"rx":57,"fill":90,"stroke":91,"style":60},"180.0","78.0",[62,750,752],{"transform":751},"translate(98.0,30.5) scale(1.000)",[66,753],{"d":754,"fill":69,"stroke":98,"style":71},"M9 8l-4 4 4 4 M15 8l4 4-4 4",[73,756,759],{"x":757,"y":758,"style":103},"110.0","76.5","Script",[44,761],{"x":762,"y":87,"width":747,"height":748,"rx":57,"fill":763,"stroke":764,"style":60},"290.0","#e7ebef","var(--line,#cdd5e6)",[62,766,768],{"transform":767},"translate(368.0,30.5) scale(1.000)",[66,769],{"d":770,"fill":69,"stroke":771,"style":71},"M12 2.5l8.5 4.7v9.6L12 21.5l-8.5-4.7V7.2z M3.5 7.2l8.5 4.7 8.5-4.7 M12 11.9v9.6","var(--text,#172033)",[73,773,725],{"x":774,"y":758,"style":775},"380.0","font-size:13px;font-weight:700;fill:var(--text,#172033);text-anchor:middle",[44,777],{"x":778,"y":87,"width":747,"height":748,"rx":57,"fill":763,"stroke":764,"style":60},"560.0",[62,780,782],{"transform":781},"translate(638.0,30.5) scale(1.000)",[66,783],{"d":784,"fill":69,"stroke":771,"style":71},"M12 3c-4 0-4 1.6-4 3v2h4.2v1H6c-2 0-3 1.4-3 4s1 4 3 4h2v-2.6c0-1.6 1.3-2.9 2.9-2.9h4.2c1.3 0 2.4-1.1 2.4-2.4V6c0-1.6-1.8-3-5.5-3z M10 5.5h.01 M12 21c4 0 4-1.6 4-3v-2h-4.2v-1H18c2 0 3-1.4 3-4s-1-4-3-4h-2v2.6c0 1.6-1.3 2.9-2.9 2.9H8.9c-1.3 0-2.4 1.1-2.4 2.4V18c0 1.6 1.8 3 5.5 3z M14 18.5h.01",[73,786,788],{"x":787,"y":758,"style":775},"650.0","openpyxl",[210,790],{"x1":757,"y1":107,"x2":757,"y2":791,"stroke":764,"style":792},"402.0","stroke-width:1.5px;stroke-dasharray:5 4",[210,794],{"x1":774,"y1":107,"x2":774,"y2":791,"stroke":764,"style":792},[210,796],{"x1":787,"y1":107,"x2":787,"y2":791,"stroke":764,"style":792},[210,798],{"x1":799,"y1":800,"x2":801,"y2":800,"stroke":91,"style":60},"115.0","134.0","371.0",[114,803],{"points":804,"fill":117},"375.0,134.0 366.0,139.0 366.0,129.0",[44,806],{"x":807,"y":808,"width":809,"height":810,"rx":811,"fill":50,"stroke":764,"style":812},"177.5","105.0","135.1","21.0","10","stroke-width:1.2px",[73,814,818],{"x":815,"y":816,"style":817},"245.0","119.5","font-size:11.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","1. create from bytes",[210,820],{"x1":799,"y1":821,"x2":822,"y2":821,"stroke":91,"style":60},"182.0","641.0",[114,824],{"points":825,"fill":117},"645.0,182.0 636.0,187.0 636.0,177.0",[44,827],{"x":828,"y":829,"width":830,"height":810,"rx":811,"fill":50,"stroke":764,"style":812},"287.1","153.0","185.7",[73,832,834],{"x":774,"y":833,"style":817},"167.5","2. Image(buffer), add_image",[210,836],{"x1":837,"y1":838,"x2":839,"y2":838,"stroke":91,"style":60},"645.0","230.0","389.0",[114,841],{"points":842,"fill":117},"385.0,230.0 394.0,225.0 394.0,235.0",[44,844],{"x":845,"y":846,"width":847,"height":810,"rx":811,"fill":50,"stroke":764,"style":812},"438.1","201.0","153.9",[73,849,852],{"x":850,"y":851,"style":817},"515.0","215.5","3. read size and format",[210,854],{"x1":799,"y1":855,"x2":801,"y2":855,"stroke":91,"style":60},"278.0",[114,857],{"points":858,"fill":117},"375.0,278.0 366.0,283.0 366.0,273.0",[44,860],{"x":861,"y":862,"width":863,"height":810,"rx":811,"fill":169,"stroke":170,"style":812},"172.2","249.0","145.7",[73,865,868],{"x":815,"y":866,"style":867},"263.5","font-size:11.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","4. with block closes it",[210,870],{"x1":799,"y1":871,"x2":822,"y2":871,"stroke":91,"style":60},"326.0",[114,873],{"points":874,"fill":117},"645.0,326.0 636.0,331.0 636.0,321.0",[44,876],{"x":877,"y":878,"width":879,"height":810,"rx":811,"fill":50,"stroke":764,"style":812},"334.2","297.0","91.6",[73,881,883],{"x":774,"y":882,"style":817},"311.5","5. wb.save()",[210,885],{"x1":837,"y1":886,"x2":839,"y2":886,"stroke":91,"style":60},"374.0",[114,888],{"points":889,"fill":117},"385.0,374.0 394.0,369.0 394.0,379.0",[44,891],{"x":892,"y":893,"width":894,"height":810,"rx":811,"fill":169,"stroke":170,"style":812},"430.4","345.0","169.1",[73,896,898],{"x":850,"y":897,"style":867},"359.5","6. read data → ValueError",[193,900,902],{"id":901},"step-3-common-sources-of-image-bytes","Step 3 — Common sources of image bytes",[10,904,905,906,908],{},"The pattern is the same whatever the source; only the way you obtain the bytes changes. A download returns them in ",[205,907,109],{},":",[198,910,912],{"className":234,"code":911,"language":236,"meta":203,"style":203},"import requests\n\nresponse = requests.get(\"https:\u002F\u002Fexample.com\u002Flogo.png\", timeout=10)\nresponse.raise_for_status()\nif not response.headers.get(\"Content-Type\", \"\").startswith(\"image\u002F\"):\n    raise ValueError(\"not an image\")\nweb_logo = Image(io.BytesIO(response.content))\n",[205,913,914,921,925,949,954,982,996],{"__ignoreMap":203},[208,915,916,918],{"class":210,"line":211},[208,917,244],{"class":243},[208,919,920],{"class":247}," requests\n",[208,922,923],{"class":210,"line":251},[208,924,271],{"emptyLinePlaceholder":270},[208,926,927,930,932,935,938,940,943,945,947],{"class":210,"line":259},[208,928,929],{"class":247},"response ",[208,931,340],{"class":243},[208,933,934],{"class":247}," requests.get(",[208,936,937],{"class":218},"\"https:\u002F\u002Fexample.com\u002Flogo.png\"",[208,939,357],{"class":247},[208,941,942],{"class":420},"timeout",[208,944,340],{"class":243},[208,946,811],{"class":306},[208,948,429],{"class":247},[208,950,951],{"class":210,"line":267},[208,952,953],{"class":247},"response.raise_for_status()\n",[208,955,956,959,962,965,968,970,973,976,979],{"class":210,"line":274},[208,957,958],{"class":243},"if",[208,960,961],{"class":243}," not",[208,963,964],{"class":247}," response.headers.get(",[208,966,967],{"class":218},"\"Content-Type\"",[208,969,357],{"class":247},[208,971,972],{"class":218},"\"\"",[208,974,975],{"class":247},").startswith(",[208,977,978],{"class":218},"\"image\u002F\"",[208,980,981],{"class":247},"):\n",[208,983,984,987,989,991,994],{"class":210,"line":288},[208,985,986],{"class":243},"    raise",[208,988,702],{"class":306},[208,990,351],{"class":247},[208,992,993],{"class":218},"\"not an image\"",[208,995,429],{"class":247},[208,997,998,1001,1003],{"class":210,"line":301},[208,999,1000],{"class":247},"web_logo ",[208,1002,340],{"class":243},[208,1004,1005],{"class":247}," Image(io.BytesIO(response.content))\n",[10,1007,1008],{},"An API field or a data URL carries base64 text, and a database column carries raw bytes:",[198,1010,1012],{"className":234,"code":1011,"language":236,"meta":203,"style":203},"api_field = \"data:image\u002Fpng;base64,\" + base64.b64encode(logo_bytes).decode()\nfrom_api = base64.b64decode(api_field.split(\",\", 1)[1])\n\ndb = sqlite3.connect(\":memory:\")\ndb.execute(\"CREATE TABLE products (sku TEXT, photo BLOB)\")\ndb.execute(\"INSERT INTO products VALUES (?, ?)\", (\"A-1\", logo_bytes))\n(from_db,) = db.execute(\"SELECT photo FROM products WHERE sku = 'A-1'\").fetchone()\nprint(from_api == logo_bytes, from_db == logo_bytes)      # True True\n",[205,1013,1014,1030,1056,1060,1075,1085,1101,1117],{"__ignoreMap":203},[208,1015,1016,1019,1021,1024,1027],{"class":210,"line":211},[208,1017,1018],{"class":247},"api_field ",[208,1020,340],{"class":243},[208,1022,1023],{"class":218}," \"data:image\u002Fpng;base64,\"",[208,1025,1026],{"class":243}," +",[208,1028,1029],{"class":247}," base64.b64encode(logo_bytes).decode()\n",[208,1031,1032,1035,1037,1040,1043,1045,1048,1051,1053],{"class":210,"line":251},[208,1033,1034],{"class":247},"from_api ",[208,1036,340],{"class":243},[208,1038,1039],{"class":247}," base64.b64decode(api_field.split(",[208,1041,1042],{"class":218},"\",\"",[208,1044,357],{"class":247},[208,1046,1047],{"class":306},"1",[208,1049,1050],{"class":247},")[",[208,1052,1047],{"class":306},[208,1054,1055],{"class":247},"])\n",[208,1057,1058],{"class":210,"line":259},[208,1059,271],{"emptyLinePlaceholder":270},[208,1061,1062,1065,1067,1070,1073],{"class":210,"line":267},[208,1063,1064],{"class":247},"db ",[208,1066,340],{"class":243},[208,1068,1069],{"class":247}," sqlite3.connect(",[208,1071,1072],{"class":218},"\":memory:\"",[208,1074,429],{"class":247},[208,1076,1077,1080,1083],{"class":210,"line":274},[208,1078,1079],{"class":247},"db.execute(",[208,1081,1082],{"class":218},"\"CREATE TABLE products (sku TEXT, photo BLOB)\"",[208,1084,429],{"class":247},[208,1086,1087,1089,1092,1095,1098],{"class":210,"line":288},[208,1088,1079],{"class":247},[208,1090,1091],{"class":218},"\"INSERT INTO products VALUES (?, ?)\"",[208,1093,1094],{"class":247},", (",[208,1096,1097],{"class":218},"\"A-1\"",[208,1099,1100],{"class":247},", logo_bytes))\n",[208,1102,1103,1106,1108,1111,1114],{"class":210,"line":301},[208,1104,1105],{"class":247},"(from_db,) ",[208,1107,340],{"class":243},[208,1109,1110],{"class":247}," db.execute(",[208,1112,1113],{"class":218},"\"SELECT photo FROM products WHERE sku = 'A-1'\"",[208,1115,1116],{"class":247},").fetchone()\n",[208,1118,1119,1121,1124,1127,1130,1132,1135],{"class":210,"line":322},[208,1120,487],{"class":306},[208,1122,1123],{"class":247},"(from_api ",[208,1125,1126],{"class":243},"==",[208,1128,1129],{"class":247}," logo_bytes, from_db ",[208,1131,1126],{"class":243},[208,1133,1134],{"class":247}," logo_bytes)      ",[208,1136,1137],{"class":507},"# True True\n",[10,1139,1140,1141,1144],{},"Check the content type or try opening the bytes with Pillow before inserting: an HTML error page saved as ",[205,1142,1143],{},"logo.png"," produces a confusing error at save time instead of a clear one at download time.",[193,1146,1148],{"id":1147},"step-4-resize-and-convert-with-pillow-in-memory","Step 4 — Resize and convert with Pillow in memory",[10,1150,1151],{},"Display size does not shrink the file: a 4000-pixel photo shown at 200 pixels is still stored at full size in every workbook. Resize the pixels, and convert formats Excel may not display, before inserting:",[198,1153,1155],{"className":234,"code":1154,"language":236,"meta":203,"style":203},"def prepare(data, max_px=400, fmt=\"PNG\"):\n    picture = PILImage.open(io.BytesIO(data))\n    picture.thumbnail((max_px, max_px))\n    if fmt == \"JPEG\" and picture.mode in (\"RGBA\", \"P\"):\n        picture = picture.convert(\"RGB\")\n    out = io.BytesIO()\n    picture.save(out, format=fmt, optimize=True)\n    return out.getvalue()\n\nbig_photo = make_logo(\"PHOTO\", size=(3000, 2000))\nsmall = prepare(big_photo, max_px=400)\nprint(len(big_photo) > len(small), PILImage.open(io.BytesIO(small)).size)   # True (400, 267)\n",[205,1156,1157,1181,1190,1195,1230,1244,1253,1275,1282,1286,1319,1338],{"__ignoreMap":203},[208,1158,1159,1161,1164,1167,1169,1172,1175,1177,1179],{"class":210,"line":211},[208,1160,330],{"class":243},[208,1162,1163],{"class":333}," prepare",[208,1165,1166],{"class":247},"(data, max_px",[208,1168,340],{"class":243},[208,1170,1171],{"class":306},"400",[208,1173,1174],{"class":247},", fmt",[208,1176,340],{"class":243},[208,1178,454],{"class":218},[208,1180,981],{"class":247},[208,1182,1183,1185,1187],{"class":210,"line":251},[208,1184,389],{"class":247},[208,1186,340],{"class":243},[208,1188,1189],{"class":247}," PILImage.open(io.BytesIO(data))\n",[208,1191,1192],{"class":210,"line":259},[208,1193,1194],{"class":247},"    picture.thumbnail((max_px, max_px))\n",[208,1196,1197,1200,1203,1205,1208,1211,1214,1217,1220,1223,1225,1228],{"class":210,"line":267},[208,1198,1199],{"class":243},"    if",[208,1201,1202],{"class":247}," fmt ",[208,1204,1126],{"class":243},[208,1206,1207],{"class":218}," \"JPEG\"",[208,1209,1210],{"class":243}," and",[208,1212,1213],{"class":247}," picture.mode ",[208,1215,1216],{"class":243},"in",[208,1218,1219],{"class":247}," (",[208,1221,1222],{"class":218},"\"RGBA\"",[208,1224,357],{"class":247},[208,1226,1227],{"class":218},"\"P\"",[208,1229,981],{"class":247},[208,1231,1232,1235,1237,1240,1242],{"class":210,"line":274},[208,1233,1234],{"class":247},"        picture ",[208,1236,340],{"class":243},[208,1238,1239],{"class":247}," picture.convert(",[208,1241,397],{"class":218},[208,1243,429],{"class":247},[208,1245,1246,1249,1251],{"class":210,"line":288},[208,1247,1248],{"class":247},"    out ",[208,1250,340],{"class":243},[208,1252,440],{"class":247},[208,1254,1255,1258,1260,1262,1265,1268,1270,1273],{"class":210,"line":301},[208,1256,1257],{"class":247},"    picture.save(out, ",[208,1259,449],{"class":420},[208,1261,340],{"class":243},[208,1263,1264],{"class":247},"fmt, ",[208,1266,1267],{"class":420},"optimize",[208,1269,340],{"class":243},[208,1271,1272],{"class":306},"True",[208,1274,429],{"class":247},[208,1276,1277,1279],{"class":210,"line":322},[208,1278,462],{"class":243},[208,1280,1281],{"class":247}," out.getvalue()\n",[208,1283,1284],{"class":210,"line":327},[208,1285,271],{"emptyLinePlaceholder":270},[208,1287,1288,1291,1293,1296,1299,1301,1304,1306,1308,1311,1313,1316],{"class":210,"line":386},[208,1289,1290],{"class":247},"big_photo ",[208,1292,340],{"class":243},[208,1294,1295],{"class":247}," make_logo(",[208,1297,1298],{"class":218},"\"PHOTO\"",[208,1300,357],{"class":247},[208,1302,1303],{"class":420},"size",[208,1305,340],{"class":243},[208,1307,351],{"class":247},[208,1309,1310],{"class":306},"3000",[208,1312,357],{"class":247},[208,1314,1315],{"class":306},"2000",[208,1317,1318],{"class":247},"))\n",[208,1320,1321,1324,1326,1329,1332,1334,1336],{"class":210,"line":403},[208,1322,1323],{"class":247},"small ",[208,1325,340],{"class":243},[208,1327,1328],{"class":247}," prepare(big_photo, ",[208,1330,1331],{"class":420},"max_px",[208,1333,340],{"class":243},[208,1335,1171],{"class":306},[208,1337,429],{"class":247},[208,1339,1340,1342,1344,1346,1349,1351,1354,1357],{"class":210,"line":432},[208,1341,487],{"class":306},[208,1343,351],{"class":247},[208,1345,492],{"class":306},[208,1347,1348],{"class":247},"(big_photo) ",[208,1350,498],{"class":243},[208,1352,1353],{"class":306}," len",[208,1355,1356],{"class":247},"(small), PILImage.open(io.BytesIO(small)).size)   ",[208,1358,1359],{"class":507},"# True (400, 267)\n",[10,1361,1362,1365,1366,1369],{},[205,1363,1364],{},"thumbnail"," keeps the aspect ratio. JPEG has no transparency, so RGBA and palette images are converted to RGB first. Formats such as WebP are accepted by openpyxl but are not displayed by older versions of Excel and by many viewers; converting everything to PNG or JPEG avoids the question. SVG cannot be read by Pillow at all — openpyxl raises ",[205,1367,1368],{},"UnidentifiedImageError"," — so render SVGs to PNG before inserting.",[25,1371,34,1376,34,1379,34,1382,34,1385,34,1389,34,1396,34,1401,34,1407,34,1413,34,1420,34,1424,34,1430,34,1434,34,1439,34,1443,34,1449,34,1451,34,1456,34,1461,34,1465,34,1469,34,1474,34,1477,34,1482,34,1485,34,1490,34,1493],{"viewBox":1372,"role":28,"ariaLabelledBy":1373,"xmlns":32,"style":33},"4 0 752 233",[1374,1375],"im-resize-t","im-resize-d",[36,1377,1378],{"id":1374},"Changing display size versus resizing the pixels",[40,1380,1381],{"id":1375},"Setting width and height on the openpyxl Image changes only how large the picture appears; the full-size bytes are still stored in the workbook. Resizing with Pillow before inserting stores a smaller image, so the workbook shrinks, at the cost of detail if someone enlarges the picture later.",[44,1383],{"x":46,"y":47,"width":48,"height":1384,"fill":50},"233",[44,1386],{"x":86,"y":87,"width":1387,"height":846,"rx":1388,"fill":121,"stroke":122,"style":60},"351.0","14",[62,1390,1392],{"transform":1391},"translate(182.5,29.0) scale(1.083)",[66,1393],{"d":1394,"fill":69,"stroke":129,"style":1395},"M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12z M12 9a3 3 0 1 0 .01 0z","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[73,1397,1400],{"x":1398,"y":748,"style":1399},"195.5","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","img.width \u002F img.height",[210,1402],{"x1":1403,"y1":1404,"x2":1405,"y2":1404,"stroke":122,"style":1406},"36.0","91.0","355.0","stroke-width:1px",[73,1408,1412],{"x":1409,"y":1410,"style":1411},"60.0","111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","one line, no extra library",[62,1414,1416],{"transform":1415},"translate(38.5,99.5) scale(0.625)",[66,1417],{"d":1418,"fill":69,"stroke":59,"style":1419},"M5 12.5l4.5 4.5L19 7","stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[73,1421,1423],{"x":1409,"y":1422,"style":1411},"137.0","full-size bytes stored",[62,1425,1427],{"transform":1426},"translate(38.5,125.5) scale(0.625)",[66,1428],{"d":1429,"fill":69,"stroke":170,"style":1419},"M6 6l12 12 M18 6L6 18",[73,1431,1433],{"x":1409,"y":1432,"style":1411},"163.0","file grows with every copy",[62,1435,1437],{"transform":1436},"translate(38.5,151.5) scale(0.625)",[66,1438],{"d":1429,"fill":69,"stroke":170,"style":1419},[73,1440,1442],{"x":1409,"y":1441,"style":1411},"189.0","fine for small logos",[62,1444,1446],{"transform":1445},"translate(38.5,177.5) scale(0.625)",[66,1447],{"d":1448,"fill":69,"stroke":122,"style":1419},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4",[44,1450],{"x":839,"y":87,"width":1387,"height":846,"rx":1388,"fill":58,"stroke":59,"style":60},[62,1452,1454],{"transform":1453},"translate(551.5,29.0) scale(1.083)",[66,1455],{"d":176,"fill":69,"stroke":70,"style":1395},[73,1457,1460],{"x":1458,"y":748,"style":1459},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Pillow thumbnail first",[210,1462],{"x1":1463,"y1":1404,"x2":1464,"y2":1404,"stroke":59,"style":1406},"405.0","724.0",[73,1466,1468],{"x":1467,"y":1410,"style":1411},"429.0","smaller workbook",[62,1470,1472],{"transform":1471},"translate(407.5,99.5) scale(0.625)",[66,1473],{"d":1418,"fill":69,"stroke":59,"style":1419},[73,1475,1476],{"x":1467,"y":1422,"style":1411},"converts formats on the way",[62,1478,1480],{"transform":1479},"translate(407.5,125.5) scale(0.625)",[66,1481],{"d":1418,"fill":69,"stroke":59,"style":1419},[73,1483,1484],{"x":1467,"y":1432,"style":1411},"detail lost if enlarged",[62,1486,1488],{"transform":1487},"translate(407.5,151.5) scale(0.625)",[66,1489],{"d":1429,"fill":69,"stroke":170,"style":1419},[73,1491,1492],{"x":1467,"y":1441,"style":1411},"right for photos",[62,1494,1496],{"transform":1495},"translate(407.5,177.5) scale(0.625)",[66,1497],{"d":1418,"fill":69,"stroke":59,"style":1419},[193,1499,1501],{"id":1500},"step-5-the-same-with-xlsxwriter","Step 5 — The same with XlsxWriter",[10,1503,1504,1505,1508,1509,1512],{},"XlsxWriter's ",[205,1506,1507],{},"insert_image"," takes a filename for its bookkeeping plus an ",[205,1510,1511],{},"image_data"," buffer with the actual bytes:",[198,1514,1516],{"className":234,"code":1515,"language":236,"meta":203,"style":203},"import xlsxwriter\n\nbook = xlsxwriter.Workbook(\"with_logo_xw.xlsx\")\nsheet = book.add_worksheet()\nsheet.insert_image(\"A1\", \"logo.png\", {\"image_data\": io.BytesIO(logo_bytes),\n                                       \"x_scale\": 0.5, \"y_scale\": 0.5})\nbook.close()\nprint(\"written\")\n",[205,1517,1518,1524,1528,1543,1553,1574,1597,1602],{"__ignoreMap":203},[208,1519,1520,1522],{"class":210,"line":211},[208,1521,244],{"class":243},[208,1523,228],{"class":247},[208,1525,1526],{"class":210,"line":251},[208,1527,271],{"emptyLinePlaceholder":270},[208,1529,1530,1533,1535,1538,1541],{"class":210,"line":259},[208,1531,1532],{"class":247},"book ",[208,1534,340],{"class":243},[208,1536,1537],{"class":247}," xlsxwriter.Workbook(",[208,1539,1540],{"class":218},"\"with_logo_xw.xlsx\"",[208,1542,429],{"class":247},[208,1544,1545,1548,1550],{"class":210,"line":267},[208,1546,1547],{"class":247},"sheet ",[208,1549,340],{"class":243},[208,1551,1552],{"class":247}," book.add_worksheet()\n",[208,1554,1555,1558,1560,1562,1565,1568,1571],{"class":210,"line":274},[208,1556,1557],{"class":247},"sheet.insert_image(",[208,1559,590],{"class":218},[208,1561,357],{"class":247},[208,1563,1564],{"class":218},"\"logo.png\"",[208,1566,1567],{"class":247},", {",[208,1569,1570],{"class":218},"\"image_data\"",[208,1572,1573],{"class":247},": io.BytesIO(logo_bytes),\n",[208,1575,1576,1579,1582,1585,1587,1590,1592,1594],{"class":210,"line":288},[208,1577,1578],{"class":218},"                                       \"x_scale\"",[208,1580,1581],{"class":247},": ",[208,1583,1584],{"class":306},"0.5",[208,1586,357],{"class":247},[208,1588,1589],{"class":218},"\"y_scale\"",[208,1591,1581],{"class":247},[208,1593,1584],{"class":306},[208,1595,1596],{"class":247},"})\n",[208,1598,1599],{"class":210,"line":301},[208,1600,1601],{"class":247},"book.close()\n",[208,1603,1604,1606,1608,1611],{"class":210,"line":322},[208,1605,487],{"class":306},[208,1607,351],{"class":247},[208,1609,1610],{"class":218},"\"written\"",[208,1612,429],{"class":247},[10,1614,1615,1616,609,1619,1622,1623,609,1625,1627,1628,23],{},"The filename is not opened; it names the image inside the file and tells XlsxWriter the format, so give it the right extension even though no such file exists. ",[205,1617,1618],{},"x_scale",[205,1620,1621],{},"y_scale"," shrink the display, like ",[205,1624,608],{},[205,1626,612],{}," in openpyxl. XlsxWriter also reads the buffer when the workbook closes, so the same keep-it-open rule applies. Placing images inside cells instead of floating above them is covered in ",[14,1629,1631],{"href":1630},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fembed-images-inside-cells-with-xlsxwriter\u002F","Embed Images Inside Cells with xlsxwriter",[193,1633,1635],{"id":1634},"step-6-one-image-many-sheets","Step 6 — One image, many sheets",[10,1637,1638,1639,1641,1642,1644],{},"A logo on every sheet of a 20-sheet report does not need 20 downloads. Keep the bytes once and create a new ",[205,1640,632],{}," object per placement — an ",[205,1643,632],{}," can only be anchored once, but the bytes can be reused:",[198,1646,1648],{"className":234,"code":1647,"language":236,"meta":203,"style":203},"wb = Workbook()\nfor name in [\"North\", \"South\", \"East\"]:\n    sheet = wb.create_sheet(name)\n    sheet.add_image(Image(io.BytesIO(logo_bytes)), \"A1\")\ndel wb[\"Sheet\"]\nwb.save(\"every_sheet.xlsx\")\nmedia = [n for n in __import__(\"zipfile\").ZipFile(\"every_sheet.xlsx\").namelist() if \"media\" in n]\nprint(len(media))                                  # 3\n",[205,1649,1650,1658,1687,1697,1706,1720,1729,1773],{"__ignoreMap":203},[208,1651,1652,1654,1656],{"class":210,"line":211},[208,1653,529],{"class":247},[208,1655,340],{"class":243},[208,1657,534],{"class":247},[208,1659,1660,1663,1666,1668,1671,1674,1676,1679,1681,1684],{"class":210,"line":251},[208,1661,1662],{"class":243},"for",[208,1664,1665],{"class":247}," name ",[208,1667,1216],{"class":243},[208,1669,1670],{"class":247}," [",[208,1672,1673],{"class":218},"\"North\"",[208,1675,357],{"class":247},[208,1677,1678],{"class":218},"\"South\"",[208,1680,357],{"class":247},[208,1682,1683],{"class":218},"\"East\"",[208,1685,1686],{"class":247},"]:\n",[208,1688,1689,1692,1694],{"class":210,"line":259},[208,1690,1691],{"class":247},"    sheet ",[208,1693,340],{"class":243},[208,1695,1696],{"class":247}," wb.create_sheet(name)\n",[208,1698,1699,1702,1704],{"class":210,"line":267},[208,1700,1701],{"class":247},"    sheet.add_image(Image(io.BytesIO(logo_bytes)), ",[208,1703,590],{"class":218},[208,1705,429],{"class":247},[208,1707,1708,1711,1714,1717],{"class":210,"line":274},[208,1709,1710],{"class":243},"del",[208,1712,1713],{"class":247}," wb[",[208,1715,1716],{"class":218},"\"Sheet\"",[208,1718,1719],{"class":247},"]\n",[208,1721,1722,1724,1727],{"class":210,"line":288},[208,1723,597],{"class":247},[208,1725,1726],{"class":218},"\"every_sheet.xlsx\"",[208,1728,429],{"class":247},[208,1730,1731,1734,1736,1739,1741,1744,1746,1749,1751,1754,1757,1759,1762,1764,1767,1770],{"class":210,"line":301},[208,1732,1733],{"class":247},"media ",[208,1735,340],{"class":243},[208,1737,1738],{"class":247}," [n ",[208,1740,1662],{"class":243},[208,1742,1743],{"class":247}," n ",[208,1745,1216],{"class":243},[208,1747,1748],{"class":306}," __import__",[208,1750,351],{"class":247},[208,1752,1753],{"class":218},"\"zipfile\"",[208,1755,1756],{"class":247},").ZipFile(",[208,1758,1726],{"class":218},[208,1760,1761],{"class":247},").namelist() ",[208,1763,958],{"class":243},[208,1765,1766],{"class":218}," \"media\"",[208,1768,1769],{"class":243}," in",[208,1771,1772],{"class":247}," n]\n",[208,1774,1775,1777,1779,1781,1784],{"class":210,"line":322},[208,1776,487],{"class":306},[208,1778,351],{"class":247},[208,1780,492],{"class":306},[208,1782,1783],{"class":247},"(media))                                  ",[208,1785,1786],{"class":507},"# 3\n",[10,1788,1789],{},"openpyxl writes a media file per placement, so the workbook grows with each copy — another reason to resize before inserting. For a report with dozens of sheets, a small, pre-sized logo keeps the file size reasonable; a full-resolution logo copied onto every sheet can add megabytes to a workbook whose data is only a few hundred kilobytes.",[193,1791,1793],{"id":1792},"step-7-check-the-images-that-were-written","Step 7 — Check the images that were written",[10,1795,1796,1797,1799,1800,1803],{},"A report that should carry a logo on every sheet is easy to check after saving: openpyxl loads the images back as ",[205,1798,632],{}," objects on each worksheet's ",[205,1801,1802],{},"_images"," list, and the file itself holds one media entry per placement:",[198,1805,1807],{"className":234,"code":1806,"language":236,"meta":203,"style":203},"check = load_workbook(\"every_sheet.xlsx\")\nfor sheet in check.worksheets:\n    print(sheet.title, len(sheet._images))\n# North 1\n# South 1\n# East 1\n",[205,1808,1809,1823,1835,1847,1852,1857],{"__ignoreMap":203},[208,1810,1811,1814,1816,1819,1821],{"class":210,"line":211},[208,1812,1813],{"class":247},"check ",[208,1815,340],{"class":243},[208,1817,1818],{"class":247}," load_workbook(",[208,1820,1726],{"class":218},[208,1822,429],{"class":247},[208,1824,1825,1827,1830,1832],{"class":210,"line":251},[208,1826,1662],{"class":243},[208,1828,1829],{"class":247}," sheet ",[208,1831,1216],{"class":243},[208,1833,1834],{"class":247}," check.worksheets:\n",[208,1836,1837,1839,1842,1844],{"class":210,"line":259},[208,1838,713],{"class":306},[208,1840,1841],{"class":247},"(sheet.title, ",[208,1843,492],{"class":306},[208,1845,1846],{"class":247},"(sheet._images))\n",[208,1848,1849],{"class":210,"line":267},[208,1850,1851],{"class":507},"# North 1\n",[208,1853,1854],{"class":210,"line":274},[208,1855,1856],{"class":507},"# South 1\n",[208,1858,1859],{"class":210,"line":288},[208,1860,1861],{"class":507},"# East 1\n",[10,1863,1864,1865,23],{},"Add this to the report's tests, together with a check on the file size, and a missing logo or an accidentally full-resolution photo is caught before the workbook is sent. Getting images out of existing workbooks — including ones you did not create — is covered in ",[14,1866,1868],{"href":1867},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fextract-images-from-an-excel-file-with-python\u002F","Extract Images from an Excel File with Python",[193,1870,1872],{"id":1871},"common-pitfalls","Common pitfalls",[1874,1875,1876,1887,1893,1899],"ul",{},[1877,1878,1879,1882,1883,1886],"li",{},[626,1880,1881],{},"A closed buffer."," The ",[205,1884,1885],{},"with io.BytesIO(...)"," pattern fails at save time. Keep buffers open until the workbook is written.",[1877,1888,1889,1892],{},[626,1890,1891],{},"Pillow missing."," openpyxl imports Pillow only when an image is used; install it in the environment that runs the report, not just on your laptop.",[1877,1894,1895,1898],{},[626,1896,1897],{},"Transparency in JPEG."," JPEG has no alpha channel; saving an RGBA image as JPEG without converting fails in Pillow. Keep logos with transparency as PNG.",[1877,1900,1901,1904],{},[626,1902,1903],{},"Huge images."," Resize before inserting; display size does not reduce file size.",[193,1906,1908],{"id":1907},"conclusion","Conclusion",[10,1910,1911,1912,1914,1915,1917,1918,1920],{},"Both openpyxl and XlsxWriter accept image bytes wrapped in ",[205,1913,518],{},", so logos, photos and API images go straight from memory into the workbook. Get the bytes from ",[205,1916,109],{},", base64-decoded fields or database BLOBs, validate them, resize and convert with Pillow, and keep every buffer open until the save — openpyxl and XlsxWriter read the data only when the file is written. Create one ",[205,1919,632],{}," per placement and reuse the bytes.",[193,1922,1924],{"id":1923},"frequently-asked-questions","Frequently asked questions",[10,1926,1927,1930],{},[626,1928,1929],{},"Can openpyxl insert an image without saving it to a file first?","\nYes. Wrap the bytes in io.BytesIO and pass that to openpyxl.drawing.image.Image instead of a path. Pillow must be installed, because openpyxl uses it to read the image's size and format.",[10,1932,1933,1936],{},[626,1934,1935],{},"Why do I get \"I\u002FO operation on closed file\" when saving?","\nopenpyxl reads the image data when the workbook is saved, not when the Image is created. If the BytesIO was closed — for example by a with block — the save fails. Keep the buffer open until after wb.save().",[10,1938,1939,1942],{},[626,1940,1941],{},"Can I insert an SVG image?","\nNot with openpyxl or XlsxWriter's standard image insertion; Pillow cannot read SVG, so openpyxl raises UnidentifiedImageError. Render the SVG to PNG first, for example with a library such as cairosvg, and insert the PNG.",[10,1944,1945,1948],{},[626,1946,1947],{},"How do I resize an image before inserting it?","\nEither set img.width and img.height in pixels on the openpyxl Image, which scales the display only, or resize the pixels with Pillow in memory first, which also makes the workbook smaller.",[193,1950,1952],{"id":1951},"related","Related",[1874,1954,1955,1961,1968,1973],{},[1877,1956,1957,1958,1960],{},"Up one level: ",[14,1959,17],{"href":16}," — pictures in reports.",[1877,1962,1963,1967],{},[14,1964,1966],{"href":1965},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fadd-logo-image-to-excel-report-with-openpyxl\u002F","Add a Logo Image to an Excel Report with openpyxl"," — the file-based basics.",[1877,1969,1970,1972],{},[14,1971,1868],{"href":1867}," — the reverse direction.",[1877,1974,1975,1979],{},[14,1976,1978],{"href":1977},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fexport-an-excel-chart-as-an-image\u002F","Export an Excel Chart as an Image with Python"," — images out of a workbook's charts.",[1981,1982,1983],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":203,"searchDepth":251,"depth":251,"links":1985},[1986,1987,1988,1989,1990,1991,1992,1993,1994,1995,1996,1997],{"id":195,"depth":251,"text":196},{"id":511,"depth":251,"text":512},{"id":620,"depth":251,"text":621},{"id":901,"depth":251,"text":902},{"id":1147,"depth":251,"text":1148},{"id":1500,"depth":251,"text":1501},{"id":1634,"depth":251,"text":1635},{"id":1792,"depth":251,"text":1793},{"id":1871,"depth":251,"text":1872},{"id":1907,"depth":251,"text":1908},{"id":1923,"depth":251,"text":1924},{"id":1951,"depth":251,"text":1952},"2026-09-23","Put images into a workbook straight from bytes — a logo downloaded over HTTP, a base64 string from an API, a photo stored in a database, a picture resized with Pillow — without writing temporary files, using openpyxl or XlsxWriter, and avoid the closed-buffer and format errors that only appear at save time.","md",[2002,2004,2006,2008],{"q":1929,"a":2003},"Yes. Wrap the bytes in io.BytesIO and pass that to openpyxl.drawing.image.Image instead of a path. Pillow must be installed, because openpyxl uses it to read the image's size and format.",{"q":1935,"a":2005},"openpyxl reads the image data when the workbook is saved, not when the Image is created. If the BytesIO was closed — for example by a with block — the save fails. Keep the buffer open until after wb.save().",{"q":1941,"a":2007},"Not with openpyxl or XlsxWriter's standard image insertion; Pillow cannot read SVG, so openpyxl raises UnidentifiedImageError. Render the SVG to PNG first, for example with a library such as cairosvg, and insert the PNG.",{"q":1947,"a":2009},"Either set img.width and img.height in pixels on the openpyxl Image, which scales the display only, or resize the pixels with Pillow in memory first, which also makes the workbook smaller.","insert an image from memory into excel with python",null,{"breadcrumb":2013},[2014,2017,2020],{"name":2015,"item":2016},"Home","\u002F",{"name":2018,"item":2019},"Formatting and Charting Excel Reports with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002F",{"name":17,"item":16},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Finsert-an-image-from-memory-into-excel",{"title":2023,"description":2024},"Insert an Image from Memory (BytesIO) into Excel with Python","Add images to Excel from bytes with openpyxl Image(BytesIO) and XlsxWriter image_data — HTTP downloads, base64 API fields, database BLOBs, Pillow resizing and format conversion, the closed BytesIO ValueError, SVG and WebP, and one image reused many times.","insert-an-image-from-memory-into-excel","formatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Finsert-an-image-from-memory-into-excel\u002Findex",[2028,788],"charts-and-images","how-to","lJWcts89pB5XL_ozE_pzHHSCx0dALuKiY8it9EYt9HU",[2032,2036],{"title":2033,"path":2034,"stem":2035,"children":-1},"Generate QR Codes and Barcodes in Excel with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fgenerate-qr-codes-and-barcodes-in-excel-with-python","formatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fgenerate-qr-codes-and-barcodes-in-excel-with-python\u002Findex",{"title":2037,"path":2038,"stem":2039,"children":-1},"Render an Excel Range as an Image with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Frender-an-excel-range-as-an-image","formatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Frender-an-excel-range-as-an-image\u002Findex",[2041,2043],{"path":2042,"title":2018},"\u002Fformatting-and-charting-excel-reports-with-python",{"path":2044,"title":17},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel",[],[2047,2050,2053,2056,2059,2062],{"path":2048,"title":2049,"kind":2029},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-dashboard-sheet-with-charts-from-multiple-tabs","Build a Dashboard Sheet with Charts from Multiple Tabs",{"path":2051,"title":2052,"kind":2029},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-rolling-12-month-chart-on-an-excel-dashboard","Build a Rolling 12-Month Chart on an Excel Dashboard",{"path":2054,"title":2055,"kind":2029},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fkeep-charts-and-images-when-filling-an-excel-template","Keep Charts and Images When Filling an Excel Template",{"path":2057,"title":2058,"kind":2029},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fadd-a-chart-to-an-excel-file-with-xlsxwriter","Add a Chart to an Excel File with xlsxwriter",{"path":2060,"title":2061,"kind":2029},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fadd-sparklines-to-an-excel-report-with-xlsxwriter","Add Sparklines to an Excel Report with xlsxwriter",{"path":2063,"title":2064,"kind":2029},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fbuild-a-waterfall-chart-in-excel-with-python","Build a Waterfall Chart in Excel with Python",1790972116786]