[{"data":1,"prerenderedAt":1710},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Frender-an-excel-range-as-an-image":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Frender-an-excel-range-as-an-image":1676,"ancestors:\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Frender-an-excel-range-as-an-image":1685,"children:\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Frender-an-excel-range-as-an-image":1690,"related:\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Frender-an-excel-range-as-an-image":1691},{"id":4,"title":5,"body":6,"dateModified":1643,"datePublished":1643,"description":1644,"extension":1645,"faq":1646,"keyword":1655,"kind":1656,"meta":1657,"navigation":281,"path":1665,"seo":1666,"slug":1669,"stem":1670,"topics":1671,"type":1674,"__hash__":1675},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Frender-an-excel-range-as-an-image\u002Findex.md","Render an Excel Range as an Image with Python",{"type":7,"value":8,"toc":1629},"minimark",[9,19,24,77,95,244,248,254,486,502,506,513,798,813,817,824,993,1000,1150,1154,1171,1175,1182,1302,1310,1469,1473,1480,1484,1538,1542,1551,1555,1564,1568,1574,1580,1586,1592,1596,1625],[10,11,12,13,18],"p",{},"A weekly report often has one block that people actually read: five KPIs, this week against last, the change in green or red. Attaching the whole workbook to a chat message or an email makes everyone open it to see those five lines. Putting a picture of the block in the message body — exactly as it looks in Excel, number formats and colours included — gets it read. Excel can \"Copy as Picture\" by hand; this page shows how to do the same from a script, both on a Linux server without Excel and on a Windows machine with it. It is 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",".",[20,21,23],"h2",{"id":22},"prerequisites","Prerequisites",[25,26,31],"pre",{"className":27,"code":28,"language":29,"meta":30,"style":30},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl pillow\n# Linux: LibreOffice and poppler for the rendering\nsudo apt install libreoffice-calc poppler-utils\n","bash","",[32,33,34,53,60],"code",{"__ignoreMap":30},[35,36,39,43,47,50],"span",{"class":37,"line":38},"line",1,[35,40,42],{"class":41},"sMTad","pip",[35,44,46],{"class":45},"srMev"," install",[35,48,49],{"class":45}," openpyxl",[35,51,52],{"class":45}," pillow\n",[35,54,56],{"class":37,"line":55},2,[35,57,59],{"class":58},"s-wDw","# Linux: LibreOffice and poppler for the rendering\n",[35,61,63,66,69,71,74],{"class":37,"line":62},3,[35,64,65],{"class":41},"sudo",[35,67,68],{"class":45}," apt",[35,70,46],{"class":45},[35,72,73],{"class":45}," libreoffice-calc",[35,75,76],{"class":45}," poppler-utils\n",[10,78,79,82,83,86,87,90,91,94],{},[32,80,81],{},"weekly_kpis.xlsx"," has a ",[32,84,85],{},"Data"," sheet and a ",[32,88,89],{},"Summary"," sheet. The part worth sharing is ",[32,92,93],{},"Summary!A3:D7",": a header row in brand colours, four KPIs with thousands separators, and a change column that shows falls in red. Below it are fifty lines of detail that must not appear in the picture.",[96,97,105,106,105,110,105,114,105,122,105,132,105,143,105,150,105,155,105,159,105,165,105,170,105,174,105,181,105,187,105,191,105,195,105,198,105,201,105,207,105,211,105,214,105,218,105,221,105,226,105,233,105,238,105,241],"svg",{"viewBox":98,"role":99,"ariaLabelledBy":100,"xmlns":103,"style":104},"4 0 752 142","img",[101,102],"ri-flow-t","ri-flow-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  ",[107,108,109],"title",{"id":101},"Rendering a range without Excel",[111,112,113],"desc",{"id":102},"The workbook is copied with only the target sheet visible and a print area set to the range, LibreOffice exports it to PDF, poppler rasterises the page at the chosen resolution, and Pillow crops away the white margins.",[115,116],"rect",{"x":117,"y":118,"width":119,"height":120,"fill":121},"4","0","752","142","#ffffff",[115,123],{"x":124,"y":125,"width":126,"height":127,"rx":128,"fill":129,"stroke":130,"style":131},"20.0","16.0","153.0","110.5","12","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[133,134,136],"g",{"transform":135},"translate(84.5,29.0) scale(1.000)",[137,138],"path",{"d":139,"fill":140,"stroke":141,"style":142},"M4 4h16v16H4z M4 9h16 M4 14h16 M10 4v16","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[144,145,149],"text",{"x":146,"y":147,"style":148},"96.5","75.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Print area",[144,151,154],{"x":146,"y":152,"style":153},"95.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","one visible sheet,",[144,156,158],{"x":146,"y":157,"style":153},"112.5","A3:D7",[37,160],{"x1":161,"y1":162,"x2":163,"y2":162,"stroke":164,"style":131},"178.0","71.2","200.0","var(--brand,#5b5cf0)",[166,167],"polygon",{"points":168,"fill":169},"204.0,71.2 195.0,76.2 195.0,66.2","#5b5cf0",[115,171],{"x":172,"y":125,"width":126,"height":127,"rx":128,"fill":173,"stroke":164,"style":131},"209.0","#f0f4ff",[133,175,177],{"transform":176},"translate(273.5,37.8) scale(1.000)",[137,178],{"d":179,"fill":140,"stroke":180,"style":142},"M6 3h9l4 4v14H6z M15 3v4h4 M9 12h6 M9 15.5h6 M9 19h4","var(--brand-strong,#4338ca)",[144,182,186],{"x":183,"y":184,"style":185},"285.5","84.2","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Export PDF",[144,188,190],{"x":183,"y":189,"style":153},"103.8","headless LibreOffice",[37,192],{"x1":193,"y1":162,"x2":194,"y2":162,"stroke":164,"style":131},"367.0","389.0",[166,196],{"points":197,"fill":169},"393.0,71.2 384.0,76.2 384.0,66.2",[115,199],{"x":200,"y":125,"width":126,"height":127,"rx":128,"fill":173,"stroke":164,"style":131},"398.0",[133,202,204],{"transform":203},"translate(462.5,37.8) scale(1.000)",[137,205],{"d":206,"fill":140,"stroke":180,"style":142},"M4 5h16v14H4z M4 16l5-5 4 4 3-3 4 4 M15 9h.01",[144,208,210],{"x":209,"y":184,"style":185},"474.5","Rasterise",[144,212,213],{"x":209,"y":189,"style":153},"pdftoppm -r 200",[37,215],{"x1":216,"y1":162,"x2":217,"y2":162,"stroke":164,"style":131},"556.0","578.0",[166,219],{"points":220,"fill":169},"582.0,71.2 573.0,76.2 573.0,66.2",[115,222],{"x":223,"y":125,"width":126,"height":127,"rx":128,"fill":224,"stroke":225,"style":131},"587.0","#d9f4f1","var(--teal,#0f9488)",[133,227,229],{"transform":228},"translate(651.5,29.0) scale(1.000)",[137,230],{"d":231,"fill":140,"stroke":232,"style":142},"M3 5h18l-7 8v6l-4 2v-8z","var(--teal-ink,#0b6157)",[144,234,237],{"x":235,"y":147,"style":236},"663.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Crop",[144,239,240],{"x":235,"y":152,"style":153},"Pillow getbbox +",[144,242,243],{"x":235,"y":157,"style":153},"padding",[20,245,247],{"id":246},"step-1-limit-the-output-to-the-range","Step 1 — Limit the output to the range",[10,249,250,251,253],{},"Everything that turns a workbook into pixels without Excel works through printing: LibreOffice renders what would be printed. So the first step is to make the printed output contain only the range. Set a print area, fit it to one page, and hide every other sheet — LibreOffice's PDF export includes every visible sheet, so without that the ",[32,252,85],{}," sheet would come first:",[25,255,259],{"className":256,"code":257,"language":258,"meta":30,"style":30},"language-python shiki shiki-themes github-light github-dark-high-contrast","from openpyxl import load_workbook\n\ndef prepare(xlsx, sheet, ref, out):\n    wb = load_workbook(xlsx)\n    target = wb[sheet]\n    for ws in wb.worksheets:\n        ws.sheet_state = \"visible\" if ws is target else \"hidden\"\n    wb.active = wb.worksheets.index(target)\n    target.print_area = ref\n    target.sheet_properties.pageSetUpPr.fitToPage = True\n    target.page_setup.fitToWidth = 1\n    target.page_setup.fitToHeight = 0\n    target.print_options.gridLines = False\n    wb.save(out)\n\nprepare(\"weekly_kpis.xlsx\", \"Summary\", \"A3:D7\", \"to_render.xlsx\")\nprint(load_workbook(\"to_render.xlsx\")[\"Summary\"].print_area)\n","python",[32,260,261,277,283,295,307,318,333,361,372,383,395,406,417,428,434,439,467],{"__ignoreMap":30},[35,262,263,267,271,274],{"class":37,"line":38},[35,264,266],{"class":265},"s-kum","from",[35,268,270],{"class":269},"skGVy"," openpyxl ",[35,272,273],{"class":265},"import",[35,275,276],{"class":269}," load_workbook\n",[35,278,279],{"class":37,"line":55},[35,280,282],{"emptyLinePlaceholder":281},true,"\n",[35,284,285,288,292],{"class":37,"line":62},[35,286,287],{"class":265},"def",[35,289,291],{"class":290},"s_Opv"," prepare",[35,293,294],{"class":269},"(xlsx, sheet, ref, out):\n",[35,296,298,301,304],{"class":37,"line":297},4,[35,299,300],{"class":269},"    wb ",[35,302,303],{"class":265},"=",[35,305,306],{"class":269}," load_workbook(xlsx)\n",[35,308,310,313,315],{"class":37,"line":309},5,[35,311,312],{"class":269},"    target ",[35,314,303],{"class":265},[35,316,317],{"class":269}," wb[sheet]\n",[35,319,321,324,327,330],{"class":37,"line":320},6,[35,322,323],{"class":265},"    for",[35,325,326],{"class":269}," ws ",[35,328,329],{"class":265},"in",[35,331,332],{"class":269}," wb.worksheets:\n",[35,334,336,339,341,344,347,349,352,355,358],{"class":37,"line":335},7,[35,337,338],{"class":269},"        ws.sheet_state ",[35,340,303],{"class":265},[35,342,343],{"class":45}," \"visible\"",[35,345,346],{"class":265}," if",[35,348,326],{"class":269},[35,350,351],{"class":265},"is",[35,353,354],{"class":269}," target ",[35,356,357],{"class":265},"else",[35,359,360],{"class":45}," \"hidden\"\n",[35,362,364,367,369],{"class":37,"line":363},8,[35,365,366],{"class":269},"    wb.active ",[35,368,303],{"class":265},[35,370,371],{"class":269}," wb.worksheets.index(target)\n",[35,373,375,378,380],{"class":37,"line":374},9,[35,376,377],{"class":269},"    target.print_area ",[35,379,303],{"class":265},[35,381,382],{"class":269}," ref\n",[35,384,386,389,391],{"class":37,"line":385},10,[35,387,388],{"class":269},"    target.sheet_properties.pageSetUpPr.fitToPage ",[35,390,303],{"class":265},[35,392,394],{"class":393},"sP0c6"," True\n",[35,396,398,401,403],{"class":37,"line":397},11,[35,399,400],{"class":269},"    target.page_setup.fitToWidth ",[35,402,303],{"class":265},[35,404,405],{"class":393}," 1\n",[35,407,409,412,414],{"class":37,"line":408},12,[35,410,411],{"class":269},"    target.page_setup.fitToHeight ",[35,413,303],{"class":265},[35,415,416],{"class":393}," 0\n",[35,418,420,423,425],{"class":37,"line":419},13,[35,421,422],{"class":269},"    target.print_options.gridLines ",[35,424,303],{"class":265},[35,426,427],{"class":393}," False\n",[35,429,431],{"class":37,"line":430},14,[35,432,433],{"class":269},"    wb.save(out)\n",[35,435,437],{"class":37,"line":436},15,[35,438,282],{"emptyLinePlaceholder":281},[35,440,442,445,448,451,454,456,459,461,464],{"class":37,"line":441},16,[35,443,444],{"class":269},"prepare(",[35,446,447],{"class":45},"\"weekly_kpis.xlsx\"",[35,449,450],{"class":269},", ",[35,452,453],{"class":45},"\"Summary\"",[35,455,450],{"class":269},[35,457,458],{"class":45},"\"A3:D7\"",[35,460,450],{"class":269},[35,462,463],{"class":45},"\"to_render.xlsx\"",[35,465,466],{"class":269},")\n",[35,468,470,473,476,478,481,483],{"class":37,"line":469},17,[35,471,472],{"class":393},"print",[35,474,475],{"class":269},"(load_workbook(",[35,477,463],{"class":45},[35,479,480],{"class":269},")[",[35,482,453],{"class":45},[35,484,485],{"class":269},"].print_area)\n",[10,487,488,489,492,493,496,497,501],{},"The target sheet is made active before the others are hidden, so the copy never has a hidden sheet as its active one — openpyxl saves that state without complaint, but Excel itself never produces it, and programs opening such a file handle it inconsistently. ",[32,490,491],{},"fitToWidth = 1"," with ",[32,494,495],{},"fitToHeight = 0"," scales a wide range down to one page width without forcing it onto one page vertically. The work is done on a copy, so the real report keeps its own print settings — ",[14,498,500],{"href":499},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fset-print-area-and-page-setup-with-openpyxl\u002F","Set the Print Area and Page Setup with openpyxl"," covers those options in full.",[20,503,505],{"id":504},"step-2-convert-to-pdf-and-rasterise","Step 2 — Convert to PDF and rasterise",[10,507,508,509,512],{},"Headless LibreOffice converts the prepared copy to PDF, and ",[32,510,511],{},"pdftoppm"," from poppler turns the page into a PNG at whatever resolution you ask for:",[25,514,516],{"className":256,"code":515,"language":258,"meta":30,"style":30},"import subprocess\nimport tempfile\nfrom pathlib import Path\n\ndef to_png(xlsx, png, dpi=200):\n    with tempfile.TemporaryDirectory() as tmp:\n        subprocess.run([\"soffice\", f\"-env:UserInstallation=file:\u002F\u002F{tmp}\u002Fprofile\", \"--headless\",\n                        \"--convert-to\", \"pdf\", \"--outdir\", tmp, xlsx],\n                       check=True, capture_output=True, timeout=120)\n        pdf = Path(tmp) \u002F (Path(xlsx).stem + \".pdf\")\n        subprocess.run([\"pdftoppm\", \"-png\", \"-r\", str(dpi), \"-singlefile\", str(pdf), str(Path(png).with_suffix(\"\"))],\n                       check=True)\n    return png\n\nto_png(\"to_render.xlsx\", \"page.png\")\nfrom PIL import Image\nprint(Image.open(\"page.png\").size)\n",[32,517,518,525,532,544,548,566,580,617,635,667,691,737,747,755,759,773,786],{"__ignoreMap":30},[35,519,520,522],{"class":37,"line":38},[35,521,273],{"class":265},[35,523,524],{"class":269}," subprocess\n",[35,526,527,529],{"class":37,"line":55},[35,528,273],{"class":265},[35,530,531],{"class":269}," tempfile\n",[35,533,534,536,539,541],{"class":37,"line":62},[35,535,266],{"class":265},[35,537,538],{"class":269}," pathlib ",[35,540,273],{"class":265},[35,542,543],{"class":269}," Path\n",[35,545,546],{"class":37,"line":297},[35,547,282],{"emptyLinePlaceholder":281},[35,549,550,552,555,558,560,563],{"class":37,"line":309},[35,551,287],{"class":265},[35,553,554],{"class":290}," to_png",[35,556,557],{"class":269},"(xlsx, png, dpi",[35,559,303],{"class":265},[35,561,562],{"class":393},"200",[35,564,565],{"class":269},"):\n",[35,567,568,571,574,577],{"class":37,"line":320},[35,569,570],{"class":265},"    with",[35,572,573],{"class":269}," tempfile.TemporaryDirectory() ",[35,575,576],{"class":265},"as",[35,578,579],{"class":269}," tmp:\n",[35,581,582,585,588,590,593,596,600,603,606,609,611,614],{"class":37,"line":335},[35,583,584],{"class":269},"        subprocess.run([",[35,586,587],{"class":45},"\"soffice\"",[35,589,450],{"class":269},[35,591,592],{"class":265},"f",[35,594,595],{"class":45},"\"-env:UserInstallation=file:\u002F\u002F",[35,597,599],{"class":598},"sSjpA","{",[35,601,602],{"class":269},"tmp",[35,604,605],{"class":598},"}",[35,607,608],{"class":45},"\u002Fprofile\"",[35,610,450],{"class":269},[35,612,613],{"class":45},"\"--headless\"",[35,615,616],{"class":269},",\n",[35,618,619,622,624,627,629,632],{"class":37,"line":363},[35,620,621],{"class":45},"                        \"--convert-to\"",[35,623,450],{"class":269},[35,625,626],{"class":45},"\"pdf\"",[35,628,450],{"class":269},[35,630,631],{"class":45},"\"--outdir\"",[35,633,634],{"class":269},", tmp, xlsx],\n",[35,636,637,641,643,646,648,651,653,655,657,660,662,665],{"class":37,"line":374},[35,638,640],{"class":639},"sa561","                       check",[35,642,303],{"class":265},[35,644,645],{"class":393},"True",[35,647,450],{"class":269},[35,649,650],{"class":639},"capture_output",[35,652,303],{"class":265},[35,654,645],{"class":393},[35,656,450],{"class":269},[35,658,659],{"class":639},"timeout",[35,661,303],{"class":265},[35,663,664],{"class":393},"120",[35,666,466],{"class":269},[35,668,669,672,674,677,680,683,686,689],{"class":37,"line":385},[35,670,671],{"class":269},"        pdf ",[35,673,303],{"class":265},[35,675,676],{"class":269}," Path(tmp) ",[35,678,679],{"class":265},"\u002F",[35,681,682],{"class":269}," (Path(xlsx).stem ",[35,684,685],{"class":265},"+",[35,687,688],{"class":45}," \".pdf\"",[35,690,466],{"class":269},[35,692,693,695,698,700,703,705,708,710,713,716,719,721,723,726,728,731,734],{"class":37,"line":397},[35,694,584],{"class":269},[35,696,697],{"class":45},"\"pdftoppm\"",[35,699,450],{"class":269},[35,701,702],{"class":45},"\"-png\"",[35,704,450],{"class":269},[35,706,707],{"class":45},"\"-r\"",[35,709,450],{"class":269},[35,711,712],{"class":393},"str",[35,714,715],{"class":269},"(dpi), ",[35,717,718],{"class":45},"\"-singlefile\"",[35,720,450],{"class":269},[35,722,712],{"class":393},[35,724,725],{"class":269},"(pdf), ",[35,727,712],{"class":393},[35,729,730],{"class":269},"(Path(png).with_suffix(",[35,732,733],{"class":45},"\"\"",[35,735,736],{"class":269},"))],\n",[35,738,739,741,743,745],{"class":37,"line":408},[35,740,640],{"class":639},[35,742,303],{"class":265},[35,744,645],{"class":393},[35,746,466],{"class":269},[35,748,749,752],{"class":37,"line":419},[35,750,751],{"class":265},"    return",[35,753,754],{"class":269}," png\n",[35,756,757],{"class":37,"line":430},[35,758,282],{"emptyLinePlaceholder":281},[35,760,761,764,766,768,771],{"class":37,"line":436},[35,762,763],{"class":269},"to_png(",[35,765,463],{"class":45},[35,767,450],{"class":269},[35,769,770],{"class":45},"\"page.png\"",[35,772,466],{"class":269},[35,774,775,777,780,783],{"class":37,"line":441},[35,776,266],{"class":265},[35,778,779],{"class":393}," PIL",[35,781,782],{"class":265}," import",[35,784,785],{"class":269}," Image\n",[35,787,788,790,793,795],{"class":37,"line":469},[35,789,472],{"class":393},[35,791,792],{"class":269},"(Image.open(",[35,794,770],{"class":45},[35,796,797],{"class":269},").size)\n",[10,799,800,801,804,805,808,809,812],{},"The ",[32,802,803],{},"-env:UserInstallation"," argument gives each conversion its own LibreOffice profile in the temporary directory. Without it, two conversions running at the same time — or a conversion while someone has LibreOffice open on the same account — share one profile, and the second can fail or exit without writing a file — which ",[32,806,807],{},"check=True"," alone does not catch, so check that the PDF exists before going on. ",[32,810,811],{},"-singlefile"," writes just the first page without a page-number suffix. At 200 dpi a US Letter page is 1700 × 2200 pixels; the range occupies only the top-left corner of it.",[20,814,816],{"id":815},"step-3-crop-to-the-content","Step 3 — Crop to the content",[10,818,819,820,823],{},"Compare the page with a plain white image of the same size; ",[32,821,822],{},"getbbox()"," on the difference returns the rectangle that contains anything that is not white. Crop to it with a little padding:",[25,825,827],{"className":256,"code":826,"language":258,"meta":30,"style":30},"from PIL import ImageChops\n\ndef crop_white(png, out, pad=12):\n    img = Image.open(png).convert(\"RGB\")\n    diff = ImageChops.difference(img, Image.new(\"RGB\", img.size, \"white\"))\n    left, top, right, bottom = diff.getbbox()\n    img.crop((max(left - pad, 0), max(top - pad, 0),\n              min(right + pad, img.width), min(bottom + pad, img.height))).save(out)\n    return Image.open(out).size\n\nprint(crop_white(\"page.png\", \"weekly_kpis.png\"))\n",[32,828,829,840,844,860,875,896,906,942,966,973,977],{"__ignoreMap":30},[35,830,831,833,835,837],{"class":37,"line":38},[35,832,266],{"class":265},[35,834,779],{"class":393},[35,836,782],{"class":265},[35,838,839],{"class":269}," ImageChops\n",[35,841,842],{"class":37,"line":55},[35,843,282],{"emptyLinePlaceholder":281},[35,845,846,848,851,854,856,858],{"class":37,"line":62},[35,847,287],{"class":265},[35,849,850],{"class":290}," crop_white",[35,852,853],{"class":269},"(png, out, pad",[35,855,303],{"class":265},[35,857,128],{"class":393},[35,859,565],{"class":269},[35,861,862,865,867,870,873],{"class":37,"line":297},[35,863,864],{"class":269},"    img ",[35,866,303],{"class":265},[35,868,869],{"class":269}," Image.open(png).convert(",[35,871,872],{"class":45},"\"RGB\"",[35,874,466],{"class":269},[35,876,877,880,882,885,887,890,893],{"class":37,"line":309},[35,878,879],{"class":269},"    diff ",[35,881,303],{"class":265},[35,883,884],{"class":269}," ImageChops.difference(img, Image.new(",[35,886,872],{"class":45},[35,888,889],{"class":269},", img.size, ",[35,891,892],{"class":45},"\"white\"",[35,894,895],{"class":269},"))\n",[35,897,898,901,903],{"class":37,"line":320},[35,899,900],{"class":269},"    left, top, right, bottom ",[35,902,303],{"class":265},[35,904,905],{"class":269}," diff.getbbox()\n",[35,907,908,911,914,917,920,923,925,928,930,933,935,937,939],{"class":37,"line":335},[35,909,910],{"class":269},"    img.crop((",[35,912,913],{"class":393},"max",[35,915,916],{"class":269},"(left ",[35,918,919],{"class":265},"-",[35,921,922],{"class":269}," pad, ",[35,924,118],{"class":393},[35,926,927],{"class":269},"), ",[35,929,913],{"class":393},[35,931,932],{"class":269},"(top ",[35,934,919],{"class":265},[35,936,922],{"class":269},[35,938,118],{"class":393},[35,940,941],{"class":269},"),\n",[35,943,944,947,950,952,955,958,961,963],{"class":37,"line":363},[35,945,946],{"class":393},"              min",[35,948,949],{"class":269},"(right ",[35,951,685],{"class":265},[35,953,954],{"class":269}," pad, img.width), ",[35,956,957],{"class":393},"min",[35,959,960],{"class":269},"(bottom ",[35,962,685],{"class":265},[35,964,965],{"class":269}," pad, img.height))).save(out)\n",[35,967,968,970],{"class":37,"line":374},[35,969,751],{"class":265},[35,971,972],{"class":269}," Image.open(out).size\n",[35,974,975],{"class":37,"line":385},[35,976,282],{"emptyLinePlaceholder":281},[35,978,979,981,984,986,988,991],{"class":37,"line":397},[35,980,472],{"class":393},[35,982,983],{"class":269},"(crop_white(",[35,985,770],{"class":45},[35,987,450],{"class":269},[35,989,990],{"class":45},"\"weekly_kpis.png\"",[35,992,895],{"class":269},[10,994,995,996,999],{},"The result is a tight PNG of the five rows: header in white on indigo, numbers right-aligned with separators, and the Returns change in red. The resolution is set by ",[32,997,998],{},"dpi"," — 200 is sharp in an email on a high-density screen; 300 suits slides.",[96,1001,105,1007,105,1010,105,1013,105,1018,105,1024,105,1030,105,1034,105,1038,105,1042,105,1046,105,1050,105,1054,105,1058,105,1063,105,1065,105,1068,105,1070,105,1073,105,1075,105,1078,105,1081,105,1085,105,1087,105,1090,105,1092,105,1095,105,1097,105,1100,105,1103,105,1107,105,1109,105,1112,105,1114,105,1117,105,1119,105,1123,105,1126,105,1130,105,1132,105,1135,105,1137,105,1140,105,1142,105,1145],{"viewBox":1002,"role":99,"ariaLabelledBy":1003,"xmlns":103,"style":1006},"149 0 462 198",[1004,1005],"ri-result-t","ri-result-d","width:100%;max-width:462px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[107,1008,1009],{"id":1004},"The rendered range — weekly_kpis.png",[111,1011,1012],{"id":1005},"The KPI table as it appears in the PNG, with the header row filled and the negative change for returns shown in red.",[115,1014],{"x":1015,"y":118,"width":1016,"height":1017,"fill":121},"149","462","198",[115,1019],{"x":1020,"y":125,"width":1021,"height":1022,"rx":118,"fill":173,"stroke":164,"style":1023},"218.6","78.1","28.0","stroke-width:1.5px",[144,1025,1029],{"x":1026,"y":1027,"style":1028},"257.7","35.0","font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","KPI",[115,1031],{"x":1032,"y":125,"width":1033,"height":1022,"rx":118,"fill":173,"stroke":164,"style":1023},"296.7","86.7",[144,1035,1037],{"x":1036,"y":1027,"style":1028},"340.1","This week",[115,1039],{"x":1040,"y":125,"width":1041,"height":1022,"rx":118,"fill":173,"stroke":164,"style":1023},"383.4","86.1",[144,1043,1045],{"x":1044,"y":1027,"style":1028},"426.5","Last week",[115,1047],{"x":1048,"y":125,"width":1049,"height":1022,"rx":118,"fill":173,"stroke":164,"style":1023},"469.6","71.8",[144,1051,1053],{"x":1052,"y":1027,"style":1028},"505.5","Change",[115,1055],{"x":1020,"y":1056,"width":1021,"height":1022,"rx":118,"fill":121,"stroke":130,"style":1057},"44.0","stroke-width:1px",[144,1059,1062],{"x":1026,"y":1060,"style":1061},"63.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Orders",[115,1064],{"x":1032,"y":1056,"width":1033,"height":1022,"rx":118,"fill":121,"stroke":130,"style":1057},[144,1066,1067],{"x":1036,"y":1060,"style":1061},"1,284",[115,1069],{"x":1040,"y":1056,"width":1041,"height":1022,"rx":118,"fill":121,"stroke":130,"style":1057},[144,1071,1072],{"x":1044,"y":1060,"style":1061},"1,190",[115,1074],{"x":1048,"y":1056,"width":1049,"height":1022,"rx":118,"fill":121,"stroke":130,"style":1057},[144,1076,1077],{"x":1052,"y":1060,"style":1061},"+7.9%",[115,1079],{"x":1020,"y":1080,"width":1021,"height":1022,"rx":118,"fill":121,"stroke":130,"style":1057},"72.0",[144,1082,1084],{"x":1026,"y":1083,"style":1061},"91.0","Revenue",[115,1086],{"x":1032,"y":1080,"width":1033,"height":1022,"rx":118,"fill":121,"stroke":130,"style":1057},[144,1088,1089],{"x":1036,"y":1083,"style":1061},"48,211",[115,1091],{"x":1040,"y":1080,"width":1041,"height":1022,"rx":118,"fill":121,"stroke":130,"style":1057},[144,1093,1094],{"x":1044,"y":1083,"style":1061},"45,020",[115,1096],{"x":1048,"y":1080,"width":1049,"height":1022,"rx":118,"fill":121,"stroke":130,"style":1057},[144,1098,1099],{"x":1052,"y":1083,"style":1061},"+7.1%",[115,1101],{"x":1020,"y":1102,"width":1021,"height":1022,"rx":118,"fill":121,"stroke":130,"style":1057},"100.0",[144,1104,1106],{"x":1026,"y":1105,"style":1061},"119.0","Returns",[115,1108],{"x":1032,"y":1102,"width":1033,"height":1022,"rx":118,"fill":121,"stroke":130,"style":1057},[144,1110,1111],{"x":1036,"y":1105,"style":1061},"37",[115,1113],{"x":1040,"y":1102,"width":1041,"height":1022,"rx":118,"fill":121,"stroke":130,"style":1057},[144,1115,1116],{"x":1044,"y":1105,"style":1061},"52",[115,1118],{"x":1048,"y":1102,"width":1049,"height":1022,"rx":118,"fill":121,"stroke":130,"style":1057},[144,1120,1122],{"x":1052,"y":1105,"style":1121},"font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","-28.8%",[115,1124],{"x":1020,"y":1125,"width":1021,"height":1022,"rx":118,"fill":121,"stroke":130,"style":1057},"128.0",[144,1127,1129],{"x":1026,"y":1128,"style":1061},"147.0","NPS",[115,1131],{"x":1032,"y":1125,"width":1033,"height":1022,"rx":118,"fill":121,"stroke":130,"style":1057},[144,1133,1134],{"x":1036,"y":1128,"style":1061},"41",[115,1136],{"x":1040,"y":1125,"width":1041,"height":1022,"rx":118,"fill":121,"stroke":130,"style":1057},[144,1138,1139],{"x":1044,"y":1128,"style":1061},"39",[115,1141],{"x":1048,"y":1125,"width":1049,"height":1022,"rx":118,"fill":121,"stroke":130,"style":1057},[144,1143,1144],{"x":1052,"y":1128,"style":1061},"+5.1%",[144,1146,1149],{"x":1147,"y":161,"style":1148},"380.0","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","What the reader sees in the message body — no attachment to open.",[20,1151,1153],{"id":1152},"step-4-use-the-picture","Step 4 — Use the picture",[10,1155,1156,1157,1160,1161,1165,1166,1170],{},"The PNG can go anywhere a picture can: inline in an HTML email with a ",[32,1158,1159],{},"cid:"," reference, attached to a Slack or Teams message, placed on a slide, or inserted into another workbook as a snapshot. For email, ",[14,1162,1164],{"href":1163},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Femail-an-excel-report-with-an-html-summary-body\u002F","Email an Excel Report with an HTML Summary Body"," shows how to embed an image so it displays inline rather than as an attachment; for chat, see ",[14,1167,1169],{"href":1168},"\u002Fautomating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fpost-an-excel-report-to-a-slack-channel-with-python\u002F","Post an Excel Report to a Slack Channel",". Always send the workbook alongside the image when people might need the detail — the picture is for reading, not for working.",[20,1172,1174],{"id":1173},"on-windows-with-excel-xlwings","On Windows with Excel: xlwings",[10,1176,1177,1178,1181],{},"When Excel is installed, it can render the range itself, which is the most faithful result — the fonts, conditional formats and sparklines are exactly what the reader would see in Excel. xlwings exposes this as ",[32,1179,1180],{},"Range.to_png",":",[25,1183,1185],{"className":256,"code":1184,"language":258,"meta":30,"style":30},"import xlwings as xw\n\nwith xw.App(visible=False) as app:\n    book = app.books.open(r\"C:\\reports\\weekly_kpis.xlsx\")\n    book.sheets[\"Summary\"].range(\"A3:D7\").to_png(r\"C:\\reports\\weekly_kpis.png\")\n    book.close()\n",[32,1186,1187,1199,1203,1227,1263,1297],{"__ignoreMap":30},[35,1188,1189,1191,1194,1196],{"class":37,"line":38},[35,1190,273],{"class":265},[35,1192,1193],{"class":269}," xlwings ",[35,1195,576],{"class":265},[35,1197,1198],{"class":269}," xw\n",[35,1200,1201],{"class":37,"line":55},[35,1202,282],{"emptyLinePlaceholder":281},[35,1204,1205,1208,1211,1214,1216,1219,1222,1224],{"class":37,"line":62},[35,1206,1207],{"class":265},"with",[35,1209,1210],{"class":269}," xw.App(",[35,1212,1213],{"class":639},"visible",[35,1215,303],{"class":265},[35,1217,1218],{"class":393},"False",[35,1220,1221],{"class":269},") ",[35,1223,576],{"class":265},[35,1225,1226],{"class":269}," app:\n",[35,1228,1229,1232,1234,1237,1240,1243,1247,1250,1253,1256,1258,1261],{"class":37,"line":297},[35,1230,1231],{"class":269},"    book ",[35,1233,303],{"class":265},[35,1235,1236],{"class":269}," app.books.open(",[35,1238,1239],{"class":265},"r",[35,1241,1242],{"class":45},"\"C:",[35,1244,1246],{"class":1245},"s_b0D","\\r",[35,1248,1249],{"class":45},"eports",[35,1251,1252],{"class":393},"\\w",[35,1254,1255],{"class":45},"eekly_kpis",[35,1257,18],{"class":393},[35,1259,1260],{"class":45},"xlsx\"",[35,1262,466],{"class":269},[35,1264,1265,1268,1270,1273,1275,1278,1280,1282,1284,1286,1288,1290,1292,1295],{"class":37,"line":309},[35,1266,1267],{"class":269},"    book.sheets[",[35,1269,453],{"class":45},[35,1271,1272],{"class":269},"].range(",[35,1274,458],{"class":45},[35,1276,1277],{"class":269},").to_png(",[35,1279,1239],{"class":265},[35,1281,1242],{"class":45},[35,1283,1246],{"class":1245},[35,1285,1249],{"class":45},[35,1287,1252],{"class":393},[35,1289,1255],{"class":45},[35,1291,18],{"class":393},[35,1293,1294],{"class":45},"png\"",[35,1296,466],{"class":269},[35,1298,1299],{"class":37,"line":320},[35,1300,1301],{"class":269},"    book.close()\n",[10,1303,1304,1305,1309],{},"Behind the scenes Excel copies the range as a picture and pastes it into a temporary chart, which is exported. That uses the clipboard, so avoid running it while someone is working in an interactive session on the same machine, and it needs a desktop session — see ",[14,1306,1308],{"href":1307},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Frun-excel-com-automation-from-task-scheduler\u002F","Run Excel COM Automation from Task Scheduler"," for scheduled runs.",[96,1311,105,1316,105,1319,105,1322,105,1325,105,1330,105,1337,105,1343,105,1347,105,1353,105,1360,105,1364,105,1369,105,1373,105,1380,105,1383,105,1389,105,1393,105,1397,105,1401,105,1406,105,1409,105,1416,105,1419,105,1424,105,1428,105,1435,105,1440,105,1444,105,1448,105,1453,105,1456,105,1461,105,1464],{"viewBox":1312,"role":99,"ariaLabelledBy":1313,"xmlns":103,"style":104},"4 0 752 207",[1314,1315],"ri-options-t","ri-options-d",[107,1317,1318],{"id":1314},"Three ways to get a picture of cells",[111,1320,1321],{"id":1315},"Comparison of rendering with LibreOffice on any platform, with Excel through xlwings on Windows, and drawing a table with matplotlib in pure Python.",[115,1323],{"x":117,"y":118,"width":119,"height":1324,"fill":121},"207",[115,1326],{"x":124,"y":125,"width":1327,"height":1328,"rx":1329,"fill":173,"stroke":164,"style":131},"228.0","175.0","14",[133,1331,1333],{"transform":1332},"translate(121.0,29.0) scale(1.083)",[137,1334],{"d":1335,"fill":140,"stroke":180,"style":1336},"M4 4h16v6H4z M4 14h16v6H4z M8 7h.01 M8 17h.01","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[144,1338,1342],{"x":1339,"y":1340,"style":1341},"134.0","78.0","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","LibreOffice + poppler",[37,1344],{"x1":1345,"y1":1083,"x2":1346,"y2":1083,"stroke":164,"style":1057},"36.0","232.0",[144,1348,1352],{"x":1349,"y":1350,"style":1351},"60.0","111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","runs on Linux servers",[133,1354,1356],{"transform":1355},"translate(38.5,99.5) scale(0.625)",[137,1357],{"d":1358,"fill":140,"stroke":225,"style":1359},"M5 12.5l4.5 4.5L19 7","stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[144,1361,1363],{"x":1349,"y":1362,"style":1351},"137.0","any resolution",[133,1365,1367],{"transform":1366},"translate(38.5,125.5) scale(0.625)",[137,1368],{"d":1358,"fill":140,"stroke":225,"style":1359},[144,1370,1372],{"x":1349,"y":1371,"style":1351},"163.0","fonts must be installed",[133,1374,1376],{"transform":1375},"translate(38.5,151.5) scale(0.625)",[137,1377],{"d":1378,"fill":140,"stroke":1379,"style":1359},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","var(--gold,#b4740a)",[115,1381],{"x":1382,"y":125,"width":1327,"height":1328,"rx":1329,"fill":224,"stroke":225,"style":131},"266.0",[133,1384,1386],{"transform":1385},"translate(367.0,29.0) scale(1.083)",[137,1387],{"d":1388,"fill":140,"stroke":232,"style":1336},"M3 5.5l8-1.2v7.2H3z M13 4l8-1.2v8.7h-8z M3 13.5h8v7.2l-8-1.2z M13 13.5h8v8.7l-8-1.2z",[144,1390,1392],{"x":1147,"y":1340,"style":1391},"font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Excel via xlwings",[37,1394],{"x1":1395,"y1":1083,"x2":1396,"y2":1083,"stroke":225,"style":1057},"282.0","478.0",[144,1398,1400],{"x":1399,"y":1350,"style":1351},"306.0","exactly what Excel shows",[133,1402,1404],{"transform":1403},"translate(284.5,99.5) scale(0.625)",[137,1405],{"d":1358,"fill":140,"stroke":225,"style":1359},[144,1407,1408],{"x":1399,"y":1362,"style":1351},"Windows with Excel only",[133,1410,1412],{"transform":1411},"translate(284.5,125.5) scale(0.625)",[137,1413],{"d":1414,"fill":140,"stroke":1415,"style":1359},"M6 6l12 12 M18 6L6 18","var(--accent,#f43f8f)",[144,1417,1418],{"x":1399,"y":1371,"style":1351},"uses the clipboard",[133,1420,1422],{"transform":1421},"translate(284.5,151.5) scale(0.625)",[137,1423],{"d":1414,"fill":140,"stroke":1415,"style":1359},[115,1425],{"x":1426,"y":125,"width":1327,"height":1328,"rx":1329,"fill":1427,"stroke":1379,"style":131},"512.0","#fdefd8",[133,1429,1431],{"transform":1430},"translate(613.0,29.0) scale(1.083)",[137,1432],{"d":1433,"fill":140,"stroke":1434,"style":1336},"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","var(--gold-ink,#7a4e06)",[144,1436,1439],{"x":1437,"y":1340,"style":1438},"626.0","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","matplotlib table",[37,1441],{"x1":1442,"y1":1083,"x2":1443,"y2":1083,"stroke":1379,"style":1057},"528.0","724.0",[144,1445,1447],{"x":1446,"y":1350,"style":1351},"552.0","no office suite at all",[133,1449,1451],{"transform":1450},"translate(530.5,99.5) scale(0.625)",[137,1452],{"d":1358,"fill":140,"stroke":225,"style":1359},[144,1454,1455],{"x":1446,"y":1362,"style":1351},"re-creates formats by hand",[133,1457,1459],{"transform":1458},"translate(530.5,125.5) scale(0.625)",[137,1460],{"d":1414,"fill":140,"stroke":1415,"style":1359},[144,1462,1463],{"x":1446,"y":1371,"style":1351},"fine for plain tables",[133,1465,1467],{"transform":1466},"translate(530.5,151.5) scale(0.625)",[137,1468],{"d":1378,"fill":140,"stroke":1379,"style":1359},[20,1470,1472],{"id":1471},"when-a-pure-python-drawing-is-enough","When a pure-Python drawing is enough",[10,1474,1475,1476,1479],{},"If the table is plain — no conditional formats, no merged headers — you can skip the office suite and draw it with matplotlib's ",[32,1477,1478],{},"table",", formatting the numbers yourself. It is quick and has no system dependencies, but every format, colour and width is re-implemented in Python, so it drifts from what the workbook shows. Use it for simple tables in environments where installing LibreOffice is not an option, and use a real renderer when the picture must match the workbook.",[20,1481,1483],{"id":1482},"common-pitfalls","Common pitfalls",[1485,1486,1487,1503,1509,1515,1528],"ul",{},[1488,1489,1490,1494,1495,1498,1499,1502],"li",{},[1491,1492,1493],"strong",{},"Serif fonts in the output."," A cell font with no name set falls back to LibreOffice's default, often a serif face. Set ",[32,1496,1497],{},"Font(name=\"Calibri\", ...)"," explicitly, and install ",[32,1500,1501],{},"fonts-crosextra-carlito",", which has Calibri's metrics, on Linux.",[1488,1504,1505,1508],{},[1491,1506,1507],{},"Other sheets in the PDF."," LibreOffice exports every visible sheet; hide the others on the copy.",[1488,1510,1511,1514],{},[1491,1512,1513],{},"Hidden active sheet."," Make the target sheet active before hiding the rest; a file whose active sheet is hidden is a state Excel never writes, and readers disagree about what to show.",[1488,1516,1517,1520,1521,1523,1524,1527],{},[1491,1518,1519],{},"Page breaks."," A tall range spills onto page two; ",[32,1522,811],{}," then keeps only the first page. Set ",[32,1525,1526],{},"fitToHeight = 1"," for ranges that must stay in one image.",[1488,1529,1530,1533,1534,1537],{},[1491,1531,1532],{},"Colours washed out."," Cell fills print only if the sheet is not set to black-and-white printing; check ",[32,1535,1536],{},"page_setup.blackAndWhite"," on templates that were set up for an office printer.",[20,1539,1541],{"id":1540},"performance-notes","Performance notes",[10,1543,1544,1545,1547,1548,1550],{},"Starting LibreOffice dominates the cost — typically around a second per conversion on a warm machine, more on the first run while it creates its profile. For many images, render one workbook with each range on its own sheet and page, then split the PDF with ",[32,1546,511],{}," (which writes one PNG per page without ",[32,1549,811],{},"), rather than starting LibreOffice once per range.",[20,1552,1554],{"id":1553},"conclusion","Conclusion",[10,1556,1557,1558,1560,1561,1563],{},"To render a range without Excel, work through printing: on a copy of the workbook, show only the target sheet, set the print area to the range and fit it to the page width; convert to PDF with headless LibreOffice using a private profile; rasterise with ",[32,1559,511],{}," at the resolution you need; and crop the white margins with Pillow. On Windows with Excel, ",[32,1562,1180],{}," in xlwings gives the most faithful picture. Either way, send the picture for reading and the workbook for detail.",[20,1565,1567],{"id":1566},"frequently-asked-questions","Frequently asked questions",[10,1569,1570,1573],{},[1491,1571,1572],{},"How do I save an Excel range as an image in Python?","\nWithout Excel, set the print area to the range with openpyxl, convert the workbook to PDF with headless LibreOffice, rasterise the page with pdftoppm and crop the whitespace with Pillow. With Excel on Windows, xlwings' Range.to_png writes the image directly.",[10,1575,1576,1579],{},[1491,1577,1578],{},"Can openpyxl export a range to an image?","\nNo. openpyxl reads and writes the file format but has no rendering engine. It is used here only to set the print area and page setup before another program does the rendering.",[10,1581,1582,1585],{},[1491,1583,1584],{},"Why does my rendered table use the wrong font?","\nThe renderer uses the fonts installed on the machine. If a cell font is not set, or the named font is missing, a substitute is used. Set font names explicitly and install metric-compatible fonts such as Carlito for Calibri on Linux.",[10,1587,1588,1591],{},[1491,1589,1590],{},"How do I get a higher-resolution image?","\nRaise the dpi passed to pdftoppm, for example -r 300. The PDF is vector, so the text stays sharp at any resolution; only the file size grows.",[20,1593,1595],{"id":1594},"related","Related",[1485,1597,1598,1604,1611,1618],{},[1488,1599,1600,1601,1603],{},"Up one level: ",[14,1602,17],{"href":16}," — pictures into and out of workbooks.",[1488,1605,1606,1610],{},[14,1607,1609],{"href":1608},"\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"," — the same job for charts.",[1488,1612,1613,1617],{},[14,1614,1616],{"href":1615},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fconvert-excel-to-pdf-on-linux-with-libreoffice\u002F","Convert Excel to PDF on Linux with LibreOffice"," — the conversion step in depth.",[1488,1619,1620,1624],{},[14,1621,1623],{"href":1622},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Ffit-an-excel-sheet-to-one-page-in-pdf\u002F","Fit an Excel Sheet to One Page in PDF"," — scaling options for the print step.",[1626,1627,1628],"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 pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}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 .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s_b0D, html code.shiki .s_b0D{--shiki-default:#22863A;--shiki-default-font-weight:bold;--shiki-dark:#72F088;--shiki-dark-font-weight:bold}",{"title":30,"searchDepth":55,"depth":55,"links":1630},[1631,1632,1633,1634,1635,1636,1637,1638,1639,1640,1641,1642],{"id":22,"depth":55,"text":23},{"id":246,"depth":55,"text":247},{"id":504,"depth":55,"text":505},{"id":815,"depth":55,"text":816},{"id":1152,"depth":55,"text":1153},{"id":1173,"depth":55,"text":1174},{"id":1471,"depth":55,"text":1472},{"id":1482,"depth":55,"text":1483},{"id":1540,"depth":55,"text":1541},{"id":1553,"depth":55,"text":1554},{"id":1566,"depth":55,"text":1567},{"id":1594,"depth":55,"text":1595},"2026-09-29","Turn a formatted block of cells — a KPI table, a summary, a small report — into a PNG for an email body, a chat message or a slide: LibreOffice on Linux via a print area and a PDF, cropped with Pillow at any resolution; xlwings to_png on Windows with Excel; and when a pure-Python rendering is enough.","md",[1647,1649,1651,1653],{"q":1572,"a":1648},"Without Excel, set the print area to the range with openpyxl, convert the workbook to PDF with headless LibreOffice, rasterise the page with pdftoppm and crop the whitespace with Pillow. With Excel on Windows, xlwings' Range.to_png writes the image directly.",{"q":1578,"a":1650},"No. openpyxl reads and writes the file format but has no rendering engine. It is used here only to set the print area and page setup before another program does the rendering.",{"q":1584,"a":1652},"The renderer uses the fonts installed on the machine. If a cell font is not set, or the named font is missing, a substitute is used. Set font names explicitly and install metric-compatible fonts such as Carlito for Calibri on Linux.",{"q":1590,"a":1654},"Raise the dpi passed to pdftoppm, for example -r 300. The PDF is vector, so the text stays sharp at any resolution; only the file size grows.","render an excel range as an image",null,{"breadcrumb":1658},[1659,1661,1664],{"name":1660,"item":679},"Home",{"name":1662,"item":1663},"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\u002Frender-an-excel-range-as-an-image",{"title":1667,"description":1668},"Save an Excel Range as a PNG Image in Python","Export a cell range to an image with Python: set the print area with openpyxl, convert with headless LibreOffice and pdftoppm, crop whitespace with Pillow; or call Range.to_png in xlwings on Windows. Includes fonts, scaling and hidden-sheet gotchas.","render-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",[1672,1673],"charts-and-images","pdf-export","how-to","vWRGOLBbjkbDSA1OxiNXBMWiAQ1L2OOebS6zPMepY3k",[1677,1681],{"title":1678,"path":1679,"stem":1680,"children":-1},"Insert an Image from Memory into Excel with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Finsert-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",{"title":1682,"path":1683,"stem":1684,"children":-1},"Resize and Position Images in Excel with openpyxl","\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fresize-and-position-images-in-excel-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fresize-and-position-images-in-excel-with-openpyxl\u002Findex",[1686,1688],{"path":1687,"title":1662},"\u002Fformatting-and-charting-excel-reports-with-python",{"path":1689,"title":17},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel",[],[1692,1695,1698,1701,1704,1707],{"path":1693,"title":1694,"kind":1674},"\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":1696,"title":1697,"kind":1674},"\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":1699,"title":1700,"kind":1674},"\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":1702,"title":1703,"kind":1674},"\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":1705,"title":1706,"kind":1674},"\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":1708,"title":1709,"kind":1674},"\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",1790972116807]